MavenParserTest.java

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package org.apache.maven.cling.invoker.mvn;

import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Map;

import org.apache.maven.api.cli.mvn.MavenOptions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;

/**
 * Unit tests for {@link MavenParser#parseMavenConfigOptions(Path)}.
 *
 * <p>The {@code .mvn/maven.config} file uses a <em>one-argument-per-line</em> format, explicitly
 * modelled after Java {@code @argfiles} (introduced by MNG-7131, commit {@code 331c5c3435},
 * July 2021). Each line is a single argv token ��� whitespace inside a line is never re-split.
 * This allows property values containing spaces to be expressed via the two-line form:
 * <pre>
 *   --define
 *   label=Apache Maven
 * </pre>
 * PR #13093 (commit {@code 2829a73de4}) broke this contract by re-tokenizing each line with
 * {@code CleanArgument.splitLine}; it was reverted in PR #13148. These tests guard against
 * that regression.
 */
class MavenParserTest {

    private final MavenParser parser = new MavenParser();

    /**
     * Core MNG-7131 regression test: a property value containing a space must survive the
     * round-trip through {@code parseMavenConfigOptions} intact when supplied via the two-line
     * {@code --define} / {@code value with spaces} form.
     *
     * <p>PR #13093 would have turned {@code "label=Apache Maven"} into two tokens
     * {@code ["label=Apache", "Maven"]}, making Commons CLI reject the parse. This test fails
     * when that bug is present.
     */
    @Test
    void testMultiLineDefineWithSpacedValue(@TempDir Path tempDir) throws Exception {
        Path config = tempDir.resolve("maven.config");
        Files.writeString(config, """
                        --define
                        label=Apache Maven
                        """, StandardCharsets.UTF_8);

        MavenOptions options = parser.parseMavenConfigOptions(config);

        assertTrue(options.userProperties().isPresent(), "userProperties must be present");
        Map<String, String> props = options.userProperties().get();
        assertEquals("Apache Maven", props.get("label"), "Space in property value must be preserved");
    }

    /**
     * Two-line {@code --define} / {@code value} form without spaces must work correctly.
     */
    @Test
    void testMultiLineDefineSimple(@TempDir Path tempDir) throws Exception {
        Path config = tempDir.resolve("maven.config");
        Files.writeString(config, """
                        --define
                        revision=1.0.0
                        """, StandardCharsets.UTF_8);

        MavenOptions options = parser.parseMavenConfigOptions(config);

        assertTrue(options.userProperties().isPresent(), "userProperties must be present");
        Map<String, String> props = options.userProperties().get();
        assertEquals("1.0.0", props.get("revision"), "Simple --define two-line form must work");
    }

    /**
     * Compact single-line {@code -Dkey=value} form must continue to work alongside the two-line form.
     */
    @Test
    void testSingleLineDefine(@TempDir Path tempDir) throws Exception {
        Path config = tempDir.resolve("maven.config");
        Files.writeString(config, """
                        -Drevision=1.0.0
                        """, StandardCharsets.UTF_8);

        MavenOptions options = parser.parseMavenConfigOptions(config);

        assertTrue(options.userProperties().isPresent(), "userProperties must be present");
        Map<String, String> props = options.userProperties().get();
        assertEquals("1.0.0", props.get("revision"), "Single-line -D form must work");
    }

    /**
     * Comment lines (starting with {@code #}) and blank lines must be ignored; only real option
     * lines are forwarded to Commons CLI.
     */
    @Test
    void testCommentsAndEmptyLinesSkipped(@TempDir Path tempDir) throws Exception {
        Path config = tempDir.resolve("maven.config");
        Files.writeString(config, """
                        # a comment

                        -Drevision=1.0.0
                        """, StandardCharsets.UTF_8);

        MavenOptions options = parser.parseMavenConfigOptions(config);

        assertTrue(options.userProperties().isPresent(), "userProperties must be present");
        Map<String, String> props = options.userProperties().get();
        assertEquals(1, props.size(), "Exactly one property must be parsed");
        assertEquals("1.0.0", props.get("revision"));
    }

    /**
     * Goals and phases are not allowed in {@code maven.config}; the parser must reject them with
     * an {@link IllegalArgumentException}.
     */
    @Test
    void testGoalRejected(@TempDir Path tempDir) throws Exception {
        Path config = tempDir.resolve("maven.config");
        Files.writeString(config, """
                        verify
                        """, StandardCharsets.UTF_8);

        assertThrows(
                IllegalArgumentException.class,
                () -> parser.parseMavenConfigOptions(config),
                "Goals must not be allowed in maven.config");
    }
}