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