View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.maven.cli;
20  
21  import java.io.ByteArrayOutputStream;
22  import java.io.File;
23  import java.io.PrintStream;
24  import java.nio.charset.StandardCharsets;
25  import java.nio.file.Path;
26  import java.nio.file.Paths;
27  import java.util.Collections;
28  import java.util.List;
29  import java.util.stream.Stream;
30  
31  import org.apache.commons.cli.CommandLine;
32  import org.apache.commons.cli.CommandLineParser;
33  import org.apache.commons.cli.DefaultParser;
34  import org.apache.commons.cli.Option;
35  import org.apache.commons.cli.Options;
36  import org.apache.commons.cli.ParseException;
37  import org.apache.maven.Maven;
38  import org.apache.maven.cli.jansi.MessageUtils;
39  import org.apache.maven.cli.transfer.ConsoleMavenTransferListener;
40  import org.apache.maven.cli.transfer.QuietMavenTransferListener;
41  import org.apache.maven.cli.transfer.Slf4jMavenTransferListener;
42  import org.apache.maven.eventspy.internal.EventSpyDispatcher;
43  import org.apache.maven.execution.MavenExecutionRequest;
44  import org.apache.maven.execution.ProfileActivation;
45  import org.apache.maven.execution.ProjectActivation;
46  import org.apache.maven.model.root.DefaultRootLocator;
47  import org.apache.maven.project.MavenProject;
48  import org.apache.maven.toolchain.building.ToolchainsBuildingRequest;
49  import org.apache.maven.toolchain.building.ToolchainsBuildingResult;
50  import org.codehaus.plexus.DefaultPlexusContainer;
51  import org.codehaus.plexus.PlexusContainer;
52  import org.eclipse.aether.transfer.TransferListener;
53  import org.junit.jupiter.api.AfterEach;
54  import org.junit.jupiter.api.BeforeEach;
55  import org.junit.jupiter.api.Test;
56  import org.junit.jupiter.params.ParameterizedTest;
57  import org.junit.jupiter.params.provider.Arguments;
58  import org.junit.jupiter.params.provider.MethodSource;
59  import org.mockito.InOrder;
60  
61  import static java.util.Arrays.asList;
62  import static org.apache.maven.cli.MavenCli.performProfileActivation;
63  import static org.apache.maven.cli.MavenCli.performProjectActivation;
64  import static org.hamcrest.CoreMatchers.equalTo;
65  import static org.hamcrest.CoreMatchers.is;
66  import static org.hamcrest.CoreMatchers.notNullValue;
67  import static org.hamcrest.CoreMatchers.nullValue;
68  import static org.hamcrest.MatcherAssert.assertThat;
69  import static org.hamcrest.collection.IsIterableContainingInAnyOrder.containsInAnyOrder;
70  import static org.junit.jupiter.api.Assertions.assertEquals;
71  import static org.junit.jupiter.api.Assertions.assertFalse;
72  import static org.junit.jupiter.api.Assertions.assertThrows;
73  import static org.junit.jupiter.api.Assertions.assertTrue;
74  import static org.junit.jupiter.api.Assumptions.assumeTrue;
75  import static org.mockito.ArgumentMatchers.any;
76  import static org.mockito.Mockito.inOrder;
77  import static org.mockito.Mockito.mock;
78  import static org.mockito.Mockito.times;
79  
80  class MavenCliTest {
81      private MavenCli cli;
82  
83      private String origBasedir;
84  
85      @BeforeEach
86      void setUp() {
87          cli = new MavenCli();
88          origBasedir = System.getProperty(MavenCli.MULTIMODULE_PROJECT_DIRECTORY);
89      }
90  
91      @AfterEach
92      void tearDown() throws Exception {
93          if (origBasedir != null) {
94              System.setProperty(MavenCli.MULTIMODULE_PROJECT_DIRECTORY, origBasedir);
95          } else {
96              System.getProperties().remove(MavenCli.MULTIMODULE_PROJECT_DIRECTORY);
97          }
98      }
99  
100     @Test
101     void testPerformProfileActivation() throws ParseException {
102         final CommandLineParser parser = new DefaultParser();
103 
104         final Options options = new Options();
105         options.addOption(Option.builder(Character.toString(CLIManager.ACTIVATE_PROFILES))
106                 .hasArg()
107                 .build());
108 
109         ProfileActivation activation;
110 
111         activation = new ProfileActivation();
112         performProfileActivation(parser.parse(options, new String[] {"-P", "test1,+test2,?test3,+?test4"}), activation);
113         assertThat(activation.getRequiredActiveProfileIds(), containsInAnyOrder("test1", "test2"));
114         assertThat(activation.getOptionalActiveProfileIds(), containsInAnyOrder("test3", "test4"));
115 
116         activation = new ProfileActivation();
117         performProfileActivation(
118                 parser.parse(options, new String[] {"-P", "!test1,-test2,-?test3,!?test4"}), activation);
119         assertThat(activation.getRequiredInactiveProfileIds(), containsInAnyOrder("test1", "test2"));
120         assertThat(activation.getOptionalInactiveProfileIds(), containsInAnyOrder("test3", "test4"));
121 
122         activation = new ProfileActivation();
123         performProfileActivation(parser.parse(options, new String[] {"-P", "-test1,+test2"}), activation);
124         assertThat(activation.getRequiredActiveProfileIds(), containsInAnyOrder("test2"));
125         assertThat(activation.getRequiredInactiveProfileIds(), containsInAnyOrder("test1"));
126     }
127 
128     @Test
129     void testDetermineProjectActivation() throws ParseException {
130         final CommandLineParser parser = new DefaultParser();
131 
132         final Options options = new Options();
133         options.addOption(Option.builder(CLIManager.PROJECT_LIST).hasArg().build());
134 
135         ProjectActivation activation;
136 
137         activation = new ProjectActivation();
138         performProjectActivation(
139                 parser.parse(options, new String[] {"-pl", "test1,+test2,?test3,+?test4"}), activation);
140         assertThat(activation.getRequiredActiveProjectSelectors(), containsInAnyOrder("test1", "test2"));
141         assertThat(activation.getOptionalActiveProjectSelectors(), containsInAnyOrder("test3", "test4"));
142 
143         activation = new ProjectActivation();
144         performProjectActivation(
145                 parser.parse(options, new String[] {"-pl", "!test1,-test2,-?test3,!?test4"}), activation);
146         assertThat(activation.getRequiredInactiveProjectSelectors(), containsInAnyOrder("test1", "test2"));
147         assertThat(activation.getOptionalInactiveProjectSelectors(), containsInAnyOrder("test3", "test4"));
148 
149         activation = new ProjectActivation();
150         performProjectActivation(parser.parse(options, new String[] {"-pl", "-test1,+test2"}), activation);
151         assertThat(activation.getRequiredActiveProjectSelectors(), containsInAnyOrder("test2"));
152         assertThat(activation.getRequiredInactiveProjectSelectors(), containsInAnyOrder("test1"));
153     }
154 
155     @Test
156     void testCalculateDegreeOfConcurrency() {
157         assertThrows(IllegalArgumentException.class, () -> cli.calculateDegreeOfConcurrency("0"));
158         assertThrows(IllegalArgumentException.class, () -> cli.calculateDegreeOfConcurrency("-1"));
159         assertThrows(IllegalArgumentException.class, () -> cli.calculateDegreeOfConcurrency("0x4"));
160         assertThrows(IllegalArgumentException.class, () -> cli.calculateDegreeOfConcurrency("1.0"));
161         assertThrows(IllegalArgumentException.class, () -> cli.calculateDegreeOfConcurrency("1."));
162         assertThrows(IllegalArgumentException.class, () -> cli.calculateDegreeOfConcurrency("AA"));
163         assertThrows(IllegalArgumentException.class, () -> cli.calculateDegreeOfConcurrency("C"));
164         assertThrows(IllegalArgumentException.class, () -> cli.calculateDegreeOfConcurrency("C2.2C"));
165         assertThrows(IllegalArgumentException.class, () -> cli.calculateDegreeOfConcurrency("C2.2"));
166         assertThrows(IllegalArgumentException.class, () -> cli.calculateDegreeOfConcurrency("2C2"));
167         assertThrows(IllegalArgumentException.class, () -> cli.calculateDegreeOfConcurrency("CXXX"));
168         assertThrows(IllegalArgumentException.class, () -> cli.calculateDegreeOfConcurrency("XXXC"));
169 
170         int cpus = Runtime.getRuntime().availableProcessors();
171         assertEquals((int) (cpus * 2.2), cli.calculateDegreeOfConcurrency("2.2C"));
172         assertEquals(1, cli.calculateDegreeOfConcurrency("0.0001C"));
173         assertThrows(IllegalArgumentException.class, () -> cli.calculateDegreeOfConcurrency("-2.2C"));
174         assertThrows(IllegalArgumentException.class, () -> cli.calculateDegreeOfConcurrency("0C"));
175     }
176 
177     @Test
178     void testMavenConfig() throws Exception {
179         System.setProperty(
180                 MavenCli.MULTIMODULE_PROJECT_DIRECTORY, new File("src/test/projects/config").getCanonicalPath());
181         CliRequest request = new CliRequest(new String[0], null);
182 
183         // read .mvn/maven.config
184         cli.initialize(request);
185         cli.cli(request);
186         assertEquals("multithreaded", request.commandLine.getOptionValue(CLIManager.BUILDER));
187         assertEquals("8", request.commandLine.getOptionValue(CLIManager.THREADS));
188 
189         // override from command line
190         request = new CliRequest(new String[] {"--builder", "foobar"}, null);
191         cli.cli(request);
192         assertEquals("foobar", request.commandLine.getOptionValue("builder"));
193     }
194 
195     @Test
196     void testMavenConfigInvalid() throws Exception {
197         System.setProperty(
198                 MavenCli.MULTIMODULE_PROJECT_DIRECTORY,
199                 new File("src/test/projects/config-illegal").getCanonicalPath());
200         CliRequest request = new CliRequest(new String[0], null);
201 
202         cli.initialize(request);
203         assertThrows(ParseException.class, () -> cli.cli(request));
204     }
205 
206     /**
207      * Read .mvn/maven.config with the following definitions:
208      * <pre>
209      *   -T
210      *   3
211      *   -Drevision=1.3.0
212      *   "-Dlabel=Apache Maven"
213      * </pre>
214      * and check if the {@code -T 3} option can be overwritten via command line
215      * argument.
216      *
217      * @throws Exception in case of failure.
218      */
219     @Test
220     void testMVNConfigurationThreadCanBeOverwrittenViaCommandLine() throws Exception {
221         System.setProperty(
222                 MavenCli.MULTIMODULE_PROJECT_DIRECTORY,
223                 new File("src/test/projects/mavenConfigProperties").getCanonicalPath());
224         CliRequest request = new CliRequest(new String[] {"-T", "5"}, null);
225 
226         cli.initialize(request);
227         // read .mvn/maven.config
228         cli.cli(request);
229 
230         assertEquals("5", request.commandLine.getOptionValue(CLIManager.THREADS));
231     }
232 
233     /**
234      * Read .mvn/maven.config with the following definitions:
235      * <pre>
236      *   -T
237      *   3
238      *   -Drevision=1.3.0
239      *   "-Dlabel=Apache Maven"
240      * </pre>
241      * and check if the {@code -Drevision-1.3.0} option can be overwritten via command line
242      * argument.
243      *
244      * @throws Exception
245      */
246     @Test
247     void testMVNConfigurationDefinedPropertiesCanBeOverwrittenViaCommandLine() throws Exception {
248         System.setProperty(
249                 MavenCli.MULTIMODULE_PROJECT_DIRECTORY,
250                 new File("src/test/projects/mavenConfigProperties").getCanonicalPath());
251         CliRequest request = new CliRequest(new String[] {"-Drevision=8.1.0"}, null);
252 
253         cli.initialize(request);
254         // read .mvn/maven.config
255         cli.cli(request);
256         cli.properties(request);
257 
258         String revision = request.getUserProperties().getProperty("revision");
259         assertEquals("8.1.0", revision);
260     }
261 
262     /**
263      * Read .mvn/maven.config with the following definitions:
264      * <pre>
265      *   -T
266      *   3
267      *   -Drevision=1.3.0
268      *   "-Dlabel=Apache Maven"
269      * </pre>
270      * and check if the {@code -Drevision-1.3.0} option can be overwritten via command line
271      * argument.
272      *
273      * @throws Exception
274      */
275     @Test
276     void testMVNConfigurationCLIRepeatedPropertiesLastWins() throws Exception {
277         System.setProperty(
278                 MavenCli.MULTIMODULE_PROJECT_DIRECTORY,
279                 new File("src/test/projects/mavenConfigProperties").getCanonicalPath());
280         CliRequest request = new CliRequest(new String[] {"-Drevision=8.1.0", "-Drevision=8.2.0"}, null);
281 
282         cli.initialize(request);
283         // read .mvn/maven.config
284         cli.cli(request);
285         cli.properties(request);
286 
287         String revision = request.getUserProperties().getProperty("revision");
288         assertEquals("8.2.0", revision);
289     }
290 
291     /**
292      * Read .mvn/maven.config with the following definitions:
293      * <pre>
294      *   -T
295      *   3
296      *   -Drevision=1.3.0
297      *   "-Dlabel=Apache Maven"
298      * </pre>
299      * and check if the {@code -Drevision-1.3.0} option can be overwritten via command line argument when there are
300      * funky arguments present.
301      *
302      * @throws Exception
303      */
304     @Test
305     void testMVNConfigurationFunkyArguments() throws Exception {
306         System.setProperty(
307                 MavenCli.MULTIMODULE_PROJECT_DIRECTORY,
308                 new File("src/test/projects/mavenConfigProperties").getCanonicalPath());
309         CliRequest request = new CliRequest(
310                 new String[] {
311                     "-Drevision=8.1.0", "--file=-Dpom.xml", "\"-Dfoo=bar ", "\"-Dfoo2=bar two\"", "-Drevision=8.2.0"
312                 },
313                 null);
314 
315         cli.initialize(request);
316         // read .mvn/maven.config
317         cli.cli(request);
318         cli.properties(request);
319 
320         assertEquals("3", request.commandLine.getOptionValue(CLIManager.THREADS));
321 
322         String revision = request.getUserProperties().getProperty("revision");
323         assertEquals("8.2.0", revision);
324 
325         assertEquals("bar ", request.getUserProperties().getProperty("foo"));
326         assertEquals("bar two", request.getUserProperties().getProperty("foo2"));
327         assertEquals("Apache Maven", request.getUserProperties().getProperty("label"));
328 
329         assertEquals("-Dpom.xml", request.getCommandLine().getOptionValue(CLIManager.ALTERNATE_POM_FILE));
330     }
331 
332     @Test
333     void testStyleColors() throws Exception {
334         assumeTrue(MessageUtils.isColorEnabled(), "ANSI not supported");
335         CliRequest request;
336 
337         MessageUtils.setColorEnabled(true);
338         request = new CliRequest(new String[] {"-B"}, null);
339         cli.cli(request);
340         cli.properties(request);
341         cli.logging(request);
342         assertFalse(MessageUtils.isColorEnabled());
343 
344         MessageUtils.setColorEnabled(true);
345         request = new CliRequest(new String[] {"--non-interactive"}, null);
346         cli.cli(request);
347         cli.properties(request);
348         cli.logging(request);
349         assertFalse(MessageUtils.isColorEnabled());
350 
351         MessageUtils.setColorEnabled(true);
352         request = new CliRequest(new String[] {"--force-interactive", "--non-interactive"}, null);
353         cli.cli(request);
354         cli.properties(request);
355         cli.logging(request);
356         assertTrue(MessageUtils.isColorEnabled());
357 
358         MessageUtils.setColorEnabled(true);
359         request = new CliRequest(new String[] {"-l", "target/temp/mvn.log"}, null);
360         request.workingDirectory = "target/temp";
361         cli.cli(request);
362         cli.properties(request);
363         cli.logging(request);
364         assertFalse(MessageUtils.isColorEnabled());
365 
366         MessageUtils.setColorEnabled(false);
367         request = new CliRequest(new String[] {"-Dstyle.color=always"}, null);
368         cli.cli(request);
369         cli.properties(request);
370         cli.logging(request);
371         assertTrue(MessageUtils.isColorEnabled());
372 
373         MessageUtils.setColorEnabled(true);
374         request = new CliRequest(new String[] {"-Dstyle.color=never"}, null);
375         cli.cli(request);
376         cli.properties(request);
377         cli.logging(request);
378         assertFalse(MessageUtils.isColorEnabled());
379 
380         MessageUtils.setColorEnabled(false);
381         request = new CliRequest(new String[] {"-Dstyle.color=always", "-B", "-l", "target/temp/mvn.log"}, null);
382         request.workingDirectory = "target/temp";
383         cli.cli(request);
384         cli.properties(request);
385         cli.logging(request);
386         assertTrue(MessageUtils.isColorEnabled());
387 
388         MessageUtils.setColorEnabled(false);
389         CliRequest maybeColorRequest =
390                 new CliRequest(new String[] {"-Dstyle.color=maybe", "-B", "-l", "target/temp/mvn.log"}, null);
391         request.workingDirectory = "target/temp";
392         cli.cli(maybeColorRequest);
393         cli.properties(maybeColorRequest);
394         assertThrows(
395                 IllegalArgumentException.class, () -> cli.logging(maybeColorRequest), "maybe is not a valid option");
396     }
397 
398     /**
399      * Verifies MNG-6558
400      */
401     @Test
402     void testToolchainsBuildingEvents() throws Exception {
403         final EventSpyDispatcher eventSpyDispatcherMock = mock(EventSpyDispatcher.class);
404         MavenCli customizedMavenCli = new MavenCli() {
405             @Override
406             protected void customizeContainer(PlexusContainer container) {
407                 super.customizeContainer(container);
408                 container.addComponent(mock(Maven.class), "org.apache.maven.Maven");
409 
410                 ((DefaultPlexusContainer) container)
411                         .addPlexusInjector(Collections.emptyList(), binder -> binder.bind(EventSpyDispatcher.class)
412                                 .toInstance(eventSpyDispatcherMock));
413             }
414         };
415 
416         CliRequest cliRequest = new CliRequest(new String[] {}, null);
417 
418         customizedMavenCli.cli(cliRequest);
419         customizedMavenCli.logging(cliRequest);
420         customizedMavenCli.container(cliRequest);
421         customizedMavenCli.toolchains(cliRequest);
422 
423         InOrder orderedEventSpyDispatcherMock = inOrder(eventSpyDispatcherMock);
424         orderedEventSpyDispatcherMock
425                 .verify(eventSpyDispatcherMock, times(1))
426                 .onEvent(any(ToolchainsBuildingRequest.class));
427         orderedEventSpyDispatcherMock
428                 .verify(eventSpyDispatcherMock, times(1))
429                 .onEvent(any(ToolchainsBuildingResult.class));
430     }
431 
432     @Test
433     void resumeFromSelectorIsSuggestedWithoutGroupId() {
434         List<MavenProject> allProjects =
435                 asList(createMavenProject("group", "module-a"), createMavenProject("group", "module-b"));
436         MavenProject failedProject = allProjects.get(0);
437 
438         String selector = cli.getResumeFromSelector(allProjects, failedProject);
439 
440         assertThat(selector, is(":module-a"));
441     }
442 
443     @Test
444     void resumeFromSelectorContainsGroupIdWhenArtifactIdIsNotUnique() {
445         List<MavenProject> allProjects =
446                 asList(createMavenProject("group-a", "module"), createMavenProject("group-b", "module"));
447         MavenProject failedProject = allProjects.get(0);
448 
449         String selector = cli.getResumeFromSelector(allProjects, failedProject);
450 
451         assertThat(selector, is("group-a:module"));
452     }
453 
454     @Test
455     void verifyLocalRepositoryPath() {
456         MavenCli cli = new MavenCli();
457         CliRequest request = new CliRequest(new String[] {}, null);
458         request.commandLine = new CommandLine.Builder().build();
459         MavenExecutionRequest executionRequest;
460 
461         // Use default
462         executionRequest = cli.populateRequest(request);
463         assertThat(executionRequest.getLocalRepositoryPath(), is(nullValue()));
464 
465         // System-properties override default
466         request.getSystemProperties().setProperty(MavenCli.LOCAL_REPO_PROPERTY, "." + File.separatorChar + "custom1");
467         executionRequest = cli.populateRequest(request);
468         assertThat(executionRequest.getLocalRepositoryPath(), is(notNullValue()));
469         assertThat(executionRequest.getLocalRepositoryPath().toString(), is("." + File.separatorChar + "custom1"));
470 
471         // User-properties override system properties
472         request.getUserProperties().setProperty(MavenCli.LOCAL_REPO_PROPERTY, "." + File.separatorChar + "custom2");
473         executionRequest = cli.populateRequest(request);
474         assertThat(executionRequest.getLocalRepositoryPath(), is(notNullValue()));
475         assertThat(executionRequest.getLocalRepositoryPath().toString(), is("." + File.separatorChar + "custom2"));
476     }
477 
478     /**
479      * MNG-7032: Disable colours for {@code --version} if {@code --batch-mode} is also given.
480      * @throws Exception cli invocation.
481      */
482     @Test
483     void testVersionStringWithoutAnsi() throws Exception {
484         // given
485         // - request with version and batch mode
486         CliRequest cliRequest = new CliRequest(new String[] {"--version", "--batch-mode"}, null);
487         ByteArrayOutputStream systemOut = new ByteArrayOutputStream();
488         PrintStream oldOut = System.out;
489         System.setOut(new PrintStream(systemOut));
490 
491         // when
492         try {
493             cli.cli(cliRequest);
494         } catch (MavenCli.ExitException exitException) {
495             // expected
496         } finally {
497             // restore sysout
498             System.setOut(oldOut);
499         }
500         String versionOut = new String(systemOut.toByteArray(), StandardCharsets.UTF_8);
501 
502         // then
503         assertEquals(MessageUtils.stripAnsiCodes(versionOut), versionOut);
504     }
505 
506     @Test
507     void populatePropertiesCanContainEqualsSign() throws Exception {
508         // Arrange
509         CliRequest request = new CliRequest(new String[] {"-Dw=x=y", "validate"}, null);
510 
511         // Act
512         cli.cli(request);
513         cli.properties(request);
514 
515         // Assert
516         assertThat(request.getUserProperties().getProperty("w"), is("x=y"));
517     }
518 
519     @Test
520     void populatePropertiesSpace() throws Exception {
521         // Arrange
522         CliRequest request = new CliRequest(new String[] {"-D", "z=2", "validate"}, null);
523 
524         // Act
525         cli.cli(request);
526         cli.properties(request);
527 
528         // Assert
529         assertThat(request.getUserProperties().getProperty("z"), is("2"));
530     }
531 
532     @Test
533     void populatePropertiesShorthand() throws Exception {
534         // Arrange
535         CliRequest request = new CliRequest(new String[] {"-Dx", "validate"}, null);
536 
537         // Act
538         cli.cli(request);
539         cli.properties(request);
540 
541         // Assert
542         assertThat(request.getUserProperties().getProperty("x"), is("true"));
543     }
544 
545     @Test
546     void populatePropertiesMultiple() throws Exception {
547         // Arrange
548         CliRequest request = new CliRequest(new String[] {"-Dx=1", "-Dy", "validate"}, null);
549 
550         // Act
551         cli.cli(request);
552         cli.properties(request);
553 
554         // Assert
555         assertThat(request.getUserProperties().getProperty("x"), is("1"));
556         assertThat(request.getUserProperties().getProperty("y"), is("true"));
557     }
558 
559     @Test
560     void populatePropertiesOverwrite() throws Exception {
561         // Arrange
562         CliRequest request = new CliRequest(new String[] {"-Dx", "-Dx=false", "validate"}, null);
563 
564         // Act
565         cli.cli(request);
566         cli.properties(request);
567 
568         // Assert
569         assertThat(request.getUserProperties().getProperty("x"), is("false"));
570     }
571 
572     @Test
573     public void findRootProjectWithAttribute() {
574         Path test = Paths.get("src/test/projects/root-attribute");
575         assertEquals(test, new DefaultRootLocator().findRoot(test.resolve("child")));
576     }
577 
578     @Test
579     public void testPropertiesInterpolation() throws Exception {
580         // Arrange
581         CliRequest request = new CliRequest(
582                 new String[] {
583                     "-Dfoo=bar",
584                     "-DvalFound=s${foo}i",
585                     "-DvalNotFound=s${foz}i",
586                     "-DvalRootDirectory=${session.rootDirectory}/.mvn/foo",
587                     "-DvalTopDirectory=${session.topDirectory}/pom.xml",
588                     "-f",
589                     "${session.rootDirectory}/my-child",
590                     "prefix:3.0.0:${foo}",
591                     "validate"
592                 },
593                 null);
594         request.rootDirectory = Paths.get("myRootDirectory");
595         request.topDirectory = Paths.get("myTopDirectory");
596 
597         // Act
598         cli.cli(request);
599         cli.properties(request);
600 
601         // Assert
602         assertThat(request.getUserProperties().getProperty("valFound"), is("sbari"));
603         assertThat(request.getUserProperties().getProperty("valNotFound"), is("s${foz}i"));
604         assertThat(request.getUserProperties().getProperty("valRootDirectory"), is("myRootDirectory/.mvn/foo"));
605         assertThat(request.getUserProperties().getProperty("valTopDirectory"), is("myTopDirectory/pom.xml"));
606         assertThat(request.getCommandLine().getOptionValue('f'), is("myRootDirectory/my-child"));
607         assertThat(request.getCommandLine().getArgs(), equalTo(new String[] {"prefix:3.0.0:bar", "validate"}));
608     }
609 
610     @ParameterizedTest
611     @MethodSource("activateBatchModeArguments")
612     public void activateBatchMode(boolean ciEnv, String[] cliArgs, boolean isBatchMode) throws Exception {
613         CliRequest request = new CliRequest(cliArgs, null);
614         if (ciEnv) request.getSystemProperties().put("env.CI", "true");
615         cli.cli(request);
616 
617         boolean batchMode = !cli.populateRequest(request).isInteractiveMode();
618 
619         assertThat(batchMode, is(isBatchMode));
620     }
621 
622     public static Stream<Arguments> activateBatchModeArguments() {
623         return Stream.of(
624                 Arguments.of(false, new String[] {}, false),
625                 Arguments.of(true, new String[] {}, true),
626                 Arguments.of(true, new String[] {"--force-interactive"}, false),
627                 Arguments.of(true, new String[] {"--force-interactive", "--non-interactive"}, false),
628                 Arguments.of(true, new String[] {"--force-interactive", "--batch-mode"}, false),
629                 Arguments.of(true, new String[] {"--force-interactive", "--non-interactive", "--batch-mode"}, false),
630                 Arguments.of(false, new String[] {"--non-interactive"}, true),
631                 Arguments.of(false, new String[] {"--batch-mode"}, true),
632                 Arguments.of(false, new String[] {"--non-interactive", "--batch-mode"}, true));
633     }
634 
635     @ParameterizedTest
636     @MethodSource("calculateTransferListenerArguments")
637     public void calculateTransferListener(boolean ciEnv, String[] cliArgs, Class<TransferListener> expectedSubClass)
638             throws Exception {
639         CliRequest request = new CliRequest(cliArgs, null);
640         if (ciEnv) request.getSystemProperties().put("env.CI", "true");
641         cli.cli(request);
642         cli.logging(request);
643 
644         TransferListener transferListener = cli.populateRequest(request).getTransferListener();
645 
646         assertThat(transferListener.getClass(), is(expectedSubClass));
647     }
648 
649     public static Stream<Arguments> calculateTransferListenerArguments() {
650         return Stream.of(
651                 Arguments.of(false, new String[] {}, ConsoleMavenTransferListener.class),
652                 Arguments.of(true, new String[] {}, QuietMavenTransferListener.class),
653                 Arguments.of(false, new String[] {"-ntp"}, QuietMavenTransferListener.class),
654                 Arguments.of(false, new String[] {"--quiet"}, QuietMavenTransferListener.class),
655                 Arguments.of(true, new String[] {"--force-interactive"}, ConsoleMavenTransferListener.class),
656                 Arguments.of(
657                         true,
658                         new String[] {"--force-interactive", "--non-interactive"},
659                         ConsoleMavenTransferListener.class),
660                 Arguments.of(
661                         true, new String[] {"--force-interactive", "--batch-mode"}, ConsoleMavenTransferListener.class),
662                 Arguments.of(
663                         true,
664                         new String[] {"--force-interactive", "--non-interactive", "--batch-mode"},
665                         ConsoleMavenTransferListener.class),
666                 Arguments.of(false, new String[] {"--non-interactive"}, Slf4jMavenTransferListener.class),
667                 Arguments.of(false, new String[] {"--batch-mode"}, Slf4jMavenTransferListener.class),
668                 Arguments.of(
669                         false, new String[] {"--non-interactive", "--batch-mode"}, Slf4jMavenTransferListener.class));
670     }
671 
672     private MavenProject createMavenProject(String groupId, String artifactId) {
673         MavenProject project = new MavenProject();
674         project.setGroupId(groupId);
675         project.setArtifactId(artifactId);
676         return project;
677     }
678 }