/* * Copyright 2017 ThoughtWorks, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.thoughtworks.go.domain.materials.perforce; import java.io.StringWriter; import java.nio.charset.Charset; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import com.thoughtworks.go.domain.materials.Modification; import com.thoughtworks.go.domain.materials.ModifiedAction; import com.thoughtworks.go.domain.materials.ModifiedFile; import com.thoughtworks.go.util.ClassMockery; import com.thoughtworks.go.util.LogFixture; import com.thoughtworks.go.util.command.ConsoleResult; import org.apache.commons.io.IOUtils; import org.apache.log4j.Level; import org.jmock.Expectations; import org.jmock.integration.junit4.JMock; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.core.io.ClassPathResource; import static com.thoughtworks.go.util.LogFixture.logFixtureFor; import static org.hamcrest.core.Is.is; import static org.hamcrest.core.StringContains.containsString; import static org.junit.Assert.assertThat; import static org.junit.Assert.fail; @RunWith(JMock.class) public class P4OutputParserTest { private P4OutputParser parser; private static final SimpleDateFormat DESCRIPTION_FORMAT = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss"); private ClassMockery context; private P4Client p4Client; @Before public void setUp() throws Exception { context = new ClassMockery(); p4Client = context.mock(P4Client.class); parser = new P4OutputParser(p4Client); } @Test public void shouldRetrieveRevisionFromChangesOutput() throws Exception { String output = "Change 2 on 2008/08/19 by cceuser@connect4 'some modification message'"; long revision = parser.revisionFromChange(output); assertThat(revision, is(2L)); } @Test public void shouldThrowExceptionIfP4ReturnDifferentDateFormatWhenCannotParseFistLineOfP4Describe() throws Exception { String output = "Change 2 on 08/08/19 by cceuser@connect4 'some modification message'"; Modification modification = new Modification(); try { parser.parseFistline(modification, output, new ConsoleResult(0, new ArrayList<>(), new ArrayList<>(), new ArrayList<>(), new ArrayList<>())); } catch (P4OutputParseException e) { assertThat(e.getMessage(), containsString("Could not parse P4 describe:")); } } @Test public void shouldRetrieveModificationFromDescription() throws Exception { String output = "Change 2 by cce123user@connect4_10.18.2.31 on 2008/08/19 15:04:43\n" + "\n" + "\tAdded config file\n" + "\n" + "Affected files ...\n" + "\n" + "... //depot/cruise-config.xml#1 add\n" + "... //depot/README.txt#2 edit\n" + "... //depot/cruise-output/log.xml#1 delete\n" + ""; Modification mod = parser.modificationFromDescription(output, new ConsoleResult(0, new ArrayList<>(), new ArrayList<>(), new ArrayList<>(), new ArrayList<>())); assertThat(mod.getRevision(), is("2")); assertThat(mod.getUserName(), is("cce123user@connect4_10.18.2.31")); assertThat(mod.getModifiedTime(), is(DESCRIPTION_FORMAT.parse("2008/08/19 15:04:43"))); assertThat(mod.getComment(), is("Added config file")); List<ModifiedFile> files = mod.getModifiedFiles(); assertThat(files.size(), is(3)); assertThat(files.get(0).getAction(), is(ModifiedAction.added)); assertThat(files.get(0).getFileName(), is("cruise-config.xml")); assertThat(files.get(1).getAction(), is(ModifiedAction.modified)); assertThat(files.get(2).getAction(), is(ModifiedAction.deleted)); assertThat(files.get(2).getFileName(), is("cruise-output/log.xml")); } /* * This test reproduces a problem we saw at a customer's installation, where the changelist was really really large * It caused a frequent StackOverflow in the java regex library. */ @Test public void shouldParseChangesWithLotsOfFilesWithoutError() throws Exception { final StringWriter writer = new StringWriter(); IOUtils.copy(new ClassPathResource("/data/BIG_P4_OUTPUT.txt").getInputStream(), writer, Charset.defaultCharset()); String output = writer.toString(); Modification modification = parser.modificationFromDescription(output, new ConsoleResult(0, new ArrayList<>(), new ArrayList<>(), new ArrayList<>(), new ArrayList<>())); assertThat(modification.getModifiedFiles().size(), is(1304)); assertThat(modification.getModifiedFiles().get(0).getFileName(), is("Internal Projects/ABC/Customers3/ABC/RIP/SomeProject/data/main/config/lib/java/AdvJDBCColumnHandler.jar")); } @Test public void shouldThrowExceptionWhenCannotParseChanges() { String line = "Some line I don't understand"; try { parser.modificationFromDescription(line, new ConsoleResult(0, new ArrayList<>(), new ArrayList<>(), new ArrayList<>(), new ArrayList<>())); fail("Should throw exception if can't parse the description"); } catch (P4OutputParseException expected) { assertThat(expected.getMessage(), containsString(line)); } } @Test public void shouldParseDescriptionProperly_Bug2456() throws P4OutputParseException { String description = "Change 570548 by michael@michael_AB2-ENV-WXP-000_ABcore on 2009/01/12 14:47:04\n" + "\n" + "\tAdd a WCF CrossDomainPolicyService and CrossDomain.xml.\n" + "\tUpdate service reference.\n" + "\tUpdate app.config and template config files.\n" + "\n" + "Affected files ...\n" + "\n" + "... //APP/AB/Core/Somemv.Core/Somemv.Core.Security.ServiceContracts/ICrossDomainPolicyService.cs#1 add\n" + "... //APP/AB/Core/Somemv.Core/Somemv.Core.Security.ServiceContracts/Somemv.Core.Security.ServiceContracts.csproj#3" + " edit\n" + "... //APP/AB/Core/Somemv.Core/Somemv.Core.Security.Services/CrossDomainPolicyService.cs#1 add\n" + "... //APP/AB/Core/Somemv.Core/Somemv.Core.Security.Services/Somemv.Core.Security.Services.csproj#4 edit\n" + "... //APP/AB/Core/Somemv.Core/Somemv.Core.ServiceReferences/Somemv.Core.ServiceReferences.csproj#44 edit\n" + "... //APP/AB/Core/Somemv.Core/Somemv.Core.ServiceReferences/Service References/SecurityService/" + "SecurityService33.xsd#2 edit\n" + "... //APP/AB/Core/Somemv.Core/Somemv.Core.ServiceReferences/app.config#30 edit\n" + "... //APP/AB/Core/Somemv.Simulation/Somemv.Simulation.Coordinator.Services/Analysis/AnalysisServiceAdapter.cs#30" + " edit\n" + "... //APP/AB/Core/Somemv.Simulation/Somemv.Simulation.Services/ManageableService.cs#4 edit\n" + "... //APP/AB/Core/Products/Somemv.RemotingService/App.config#102 edit\n" + "... //APP/AB/Core/Products/Somemv.RemotingService/CrossDomain.xml#1 add\n" + "... //APP/AB/Core/Products/Somemv.RemotingService/Somemv.RemotingService.csproj#53 edit\n" + "... //APP/AB/Core/Templates/Somemv.RemotingService/app_template.config#69 edit\n" + "... //APP/AB/Core/Templates/Somemv.RemotingService/coordinator_template.config#69 edit\n" + ""; Modification modification = parser.modificationFromDescription(description, new ConsoleResult(0, new ArrayList<>(), new ArrayList<>(), new ArrayList<>(), new ArrayList<>())); assertThat(modification.getComment(), is( "Add a WCF CrossDomainPolicyService and CrossDomain.xml.\n" + "Update service reference.\n" + "Update app.config and template config files.")); } @Test public void shouldParseCommentWithAffectedFilesCorrectly() throws P4OutputParseException { String description = "Change 5 by cceuser@CceDev01 on 2009/08/06 14:21:30\n" + "\n" + "\tAffected files ...\n" + "\t\n" + "\t... //DEPOT/FILE#943 edit\n" + "\n" + "Affected files ...\n" + "\n" + "... //depot/file#5 edit"; Modification modification = parser.modificationFromDescription(description, new ConsoleResult(0, new ArrayList<>(), new ArrayList<>(), new ArrayList<>(), new ArrayList<>())); assertThat(modification.getComment(), is( "Affected files ...\n" + "\n" + "... //DEPOT/FILE#943 edit")); } @Test public void shouldParseCorrectlyWhenCommentIsEmpty() throws P4OutputParseException { String description = "Change 102 by godev@blrstdcrspair03 on 2013/06/04 12:00:35\n" + "\n" + "\n" + "Affected files ...\n" + "\n" + "... //another_depot/1.txt#6 edit"; Modification modification = parser.modificationFromDescription(description, new ConsoleResult(0, new ArrayList<>(), new ArrayList<>(), new ArrayList<>(), new ArrayList<>())); assertThat(modification.getComment(), is("")); } private static final String BUG_2503_OUTPUT = "Change 122636 by ipaipa@ipaipa-STANDARD-DHTML on 2009/02/06 17:53:57\n" + " \n" + "\tPCTD-820: Fix for grid header scrolling issue when the user has tabbed between the filter " + "input boxes.\n" + " \n" + "Jobs fixed ...\n" + " \n" + "PCTD-820 on 2009/02/09 by philipe *closed*\n" + " \n" + "\tUsing the Tab key to move through grid filters can cause grid column headers to" + " become mis-aligned\n" + " \n" + "Affected files ...\n" + "\n" + "... //APP/RF/Core/Somemv.Core/Somemv.Core#1 add\n"; @Test public void shouldMatchWhenCommentsAreMultipleLines() throws Exception { Modification modification = parser.modificationFromDescription(BUG_2503_OUTPUT, new ConsoleResult(0, new ArrayList<>(), new ArrayList<>(), new ArrayList<>(), new ArrayList<>())); assertThat(parser.revisionFromChange(BUG_2503_OUTPUT), is(122636L)); assertThat(modification.getModifiedTime(), is(DESCRIPTION_FORMAT.parse("2009/02/06 17:53:57"))); assertThat(modification.getModifiedFiles().size(), is(1)); assertThat(modification.getUserName(), is("ipaipa@ipaipa-STANDARD-DHTML")); } @Test public void shouldIgnoreEmptyLinesInChanges() throws ParseException { final String output = "Change 539921 on 2008/09/24 by abc@SomeRefinery_abc_sa1-sgr-xyz-001 'more work in progress on ABC un'\n" + "Change 539920 on 2008/09/24 by " + "abc@SomeRefinery_abc_sa1-sgr-xyz-001 'Fixed pipeline for abc-new-sale'\n" + "Change 539919 on 2008/09/24 by " + "james@SomeRefinery_def_sa1-sgr-xyz-001 'Assignment stage to send ok and'\n" + "Change 539918 on 2008/09/24 by abc@SomeRefinery_abc_sa1-sgr-xyz-001 'More refactoring and code clean'\n" + "Change 539917 on 2008/09/24 by thomas@tom_ws_stuff 'added client name for Arza SW'\n" + "Change 539916 on 2008/09/24 by alex@SA2-COUNT-LAX-001 'sending the context further '\n" + "Change 539915 on 2008/09/24 by ellen@ellen-box-1 'TT 678 cause:Old code try to'\n" + "Change 539914 on 2008/09/24 by valerie@ExcelSheets_New 'update sheet comments '\n" + "\n" + "Change 539913 on 2008/09/24 by perforce@SOM-NAME-HERE-HOST-1 'apply new version numbers via A'\n" + "Change 539912 on 2008/09/24 by lance@lance-box-1 'Corrected a typo. '\n" + "\n" + "Change 539911 on 2008/09/24 by lance@lance-box-1 'corrected a typo. '\n" + "Change 539910 on 2008/09/24 by josh@josh_box_1 'Changes to remove hacks I had m'\n" + "Change 539909 on 2008/09/24 by thomas@tom_ws_stuff 'Added Arza SW to add request '\n" + "Change 539908 on 2008/09/24 by padma@Padma '1. Fix #2644 : When the FSOi'\n" + "Change 539907 on 2008/09/24 by berlin@Dans_Box 'Added GetDocumentMetadataForEdi'\n" + "Change 539906 on 2008/09/24 by lance@lance-box-1 'Added detail aboutPFP. '\n" + "Change 539904 on 2008/09/24 by lance@lance-box-1 'Added a detail about PFP. '\n" + "Change 539903 on 2008/09/24 by nitya@docs_box 'Updated for lam, am 20080923'\n" + "Change 539902 on 2008/09/24 by abc@SomeRefinery_abc_sa1-sgr-xyz-001 'Work in progress '\n" + "Change 539901 on 2008/09/24 by anil@anil-box-1 'Added all columns of AA_TASK to'\n" + ""; context.checking(new Expectations() { { allowing(p4Client).describe(with(any(Long.class))); will(returnValue("Change 539921 by abc@SomeRefinery_abc_sa1-sgr-xyz-001 on 2008/09/24 12:10:00\n" + "\n" + "\tmore work in progress on ABC unit test\n" + "\n" + "Affected files ...\n" + "")); } }); List<Modification> modifications = parser.modifications(new ConsoleResult(0, Arrays.asList(output.split("\n")), new ArrayList<>(), new ArrayList<>(), new ArrayList<>())); assertThat(modifications.size(), is(20)); } @Test public void shouldIgnoreBadLinesAndLogThem() throws ParseException { try (LogFixture logging = logFixtureFor(P4OutputParser.class, Level.DEBUG)) { final String output = "Change 539921 on 2008/09/24 " + "by abc@SomeRefinery_abc_sa1-sgr-xyz-001 'more work in progress on MDC un'\n"; final String description = "Change that I cannot parse :-(\n"; context.checking(new Expectations() { { allowing(p4Client).describe(with(any(Long.class))); will(returnValue(description)); } }); List<Modification> modifications = parser.modifications(new ConsoleResult(0, Arrays.asList(output.split("\n")), new ArrayList<>(), new ArrayList<>(), new ArrayList<>())); assertThat(modifications.size(), is(0)); assertThat(logging.getLog(), containsString(description)); } } }