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