//
// Copyright © 2014, David Tesler (https://github.com/protobufel)
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
// * Neither the name of the <organization> nor the
// names of its contributors may be used to endorse or promote products
// derived from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
package com.github.protobufel.resource.exec.maven.plugin;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.contentOf;
import static org.junit.Assert.fail;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.PathMatcher;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Properties;
import java.util.regex.Pattern;
import org.apache.maven.model.FileSet;
import org.apache.maven.plugin.MojoExecutionException;
import org.assertj.core.api.JUnitSoftAssertions;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.hamcrest.Description;
import org.hamcrest.Matcher;
import org.hamcrest.TypeSafeMatcher;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.rules.RuleChain;
import org.junit.rules.TemporaryFolder;
import org.junit.rules.TestRule;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRule;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
// import static org.hamcrest.MatcherAssert.assertThat;
@RunWith(JUnit4.class)
public class ExecMojoTest {
private static final Logger log = LoggerFactory.getLogger(ExecMojoTest.class);
private static final List<String> SUBDIRS = Arrays.asList("dir1", "dir2/dir21/dir211",
"dir2/dir22/dir221", "dir2/dir22/dir222/dir2221");
private static final List<String> SUBFILES = Arrays.asList("file1", "file2.txt", "file3.ok",
// "file4." will become file4 on Windows!
"file4");
private static final String GLOB_ALL = "**/*";
private static final List<String> BAD_ARGS = Arrays.asList(
// "dir1/file1", // good WEIRD: Windows del won't report error in presence of any good file!
"dir1/non.existent.file" // bad
);
@Mock
private Jsr330Component component;
public final TemporaryFolder temp = new TemporaryFolder();
public final ExpectedException expected = ExpectedException.none();
public final JUnitSoftAssertions softly = new JUnitSoftAssertions();
@Rule
public final TestRule chain = RuleChain.outerRule(expected).around(new MockitoJUnitRule(this))
.around(temp).around(softly);
private File outFile;
private File errorFile;
private List<String> args;
private String execLocation;
private List<String> execArgs;
private ExecMojo mojo;
private final Map<String, String> environment = Collections.emptyMap();
private File workDir;
private Path rootDir;
private List<String> expectedFiles;
private String glob;
@Before
public void setUp() throws Exception {
// given
final Properties properties = new Properties();
try (InputStream in = getClass().getResourceAsStream("test.properties")) {
properties.load(in);
}
execLocation = properties.getProperty("plugin.test.exec");
assertThat(execLocation).isIn("rm", "del");
// errorFile = temp.newFile("errors.txt");
errorFile = new File(temp.getRoot(), "errors.txt");
// outFile = temp.newFile("result");
outFile = new File(temp.getRoot(), "result");
workDir = temp.newFolder();
expectedFiles = makeTestTree(workDir.toPath(), SUBDIRS, SUBFILES);
rootDir = workDir.toPath();
glob = GLOB_ALL;
args = new ArrayList<>();
mojo = new ExecMojo(component);
mojo.setExecLocation(execLocation);
mojo.setExecLocationAsIs(true);
mojo.setSystemCommand(true);
mojo.setArgQuote("\"");
mojo.setEnvironment(environment);
mojo.setErrorFile(errorFile);
mojo.setRedirectErrorStream(false);
mojo.setErrorInherit(false);
mojo.setErrorPipe(true);
mojo.setErrorProperty("");
mojo.setErrorAppend(false);
mojo.setOutAppend(false);
mojo.setOutFile(outFile);
mojo.setOutInherit(false);
mojo.setOutPipe(true);
mojo.setOutProperty("");
mojo.setWorkDir(workDir);
mojo.setAllowDuplicates(true);
mojo.setAllowFiles(true);
mojo.setAllowDirs(false);
mojo.setFileSets(Collections.<FileSet>emptyList());
}
private List<String> makeTestTree(final Path rootDir, final Iterable<String> subDirs,
final Iterable<String> files) throws IOException {
final Path realDir = Files.createDirectories(Objects.requireNonNull(rootDir));
for (final String subDir : Objects.requireNonNull(subDirs)) {
Files.createDirectories(realDir.resolve(Objects.requireNonNull(subDir)));
}
final List<String> allFiles = new ArrayList<>();
Files.walkFileTree(rootDir, new SimpleFileVisitor<Path>() {
@NonNullByDefault(false)
@Override
public FileVisitResult postVisitDirectory(final Path dir, final IOException exc)
throws IOException {
super.postVisitDirectory(dir, exc);
if (!dir.equals(realDir)) {
for (final String file : files) {
final Path filePath = dir.resolve(file);
if (Files.notExists(filePath)) {
allFiles.add(Files.createFile(filePath).toAbsolutePath().toString());
}
}
}
return FileVisitResult.CONTINUE;
}
});
Collections.sort(allFiles);
return Collections.unmodifiableList(allFiles);
}
@After
public void tearDown() throws Exception {}
@Test
public void testGetResourceFiles() throws Exception {
final Collection<String> actualFiles =
mojo.getResourceFiles(getFileSets(rootDir), mojo.isFollowLinks(), true, true, false);
assertThat(actualFiles).isNotNull().doesNotContainNull().hasSameSizeAs(expectedFiles)
.containsOnlyElementsOf(expectedFiles);
// the following is the same as above, but easy to compare
// final ArrayList<String> actualSortedFiles = new ArrayList<String>(actualFiles);
// Collections.sort(actualSortedFiles);
// assertThat(actualSortedFiles).isEqualTo(expectedFiles);
}
@Test
public void executeWithFileArgsGood() throws IOException {
try {
addFileArgs(rootDir, glob, args);
mojo.setArgs(args);
mojo.execute();
} catch (final MojoExecutionException e) {
fail(e.getLongMessage());
}
assertThat(contentOf(errorFile)).as("the errorFile is empty").hasSize(0);
assertDeepEmpty(rootDir);
}
@Test
public void executeWithFileArgsBad() throws IOException {
try {
addFileArgs(rootDir, BAD_ARGS, args);
mojo.setArgs(args);
mojo.execute();
} catch (final MojoExecutionException e) {
fail(e.getLongMessage());
}
final String outFileText = contentOf(outFile);
final String errorFileText = contentOf(errorFile);
log.debug("errorFileText='{}'", errorFileText);
assertThat(errorFileText).as("the errorFile's contents").contains("non.existent.file");
}
private void assertDeepEmpty(final Path dir) {
try {
Files.walkFileTree(dir, new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult visitFile(final Path file, final BasicFileAttributes attrs)
throws IOException {
fail(String.format("directory '%s' is not deeply empty - found file '%s'", dir, file));
return super.visitFile(file, attrs);
}
});
} catch (final IOException e) {
throw new RuntimeException(e);
}
}
@Test
public void executeWithFileSetsGood() throws IOException {
try {
mojo.setArgs(args);
mojo.setFileSets(getFileSets(rootDir));
mojo.execute();
} catch (final MojoExecutionException e) {
fail(e.getLongMessage());
}
assertThat(contentOf(errorFile)).as("the errorFile is empty").hasSize(0);
assertDeepEmpty(rootDir);
}
@Test
public void executeWithFileSetsWithNonExistentIncludes() throws IOException {
try {
mojo.setArgs(args);
mojo.setFileSets(getFileSetsWithNonExistentIncludes(rootDir));
mojo.execute();
} catch (final MojoExecutionException e) {
fail(e.getLongMessage());
}
assertThat(contentOf(errorFile)).as("the errorFile is empty").hasSize(0);
assertDeepEmpty(rootDir);
}
@Test
public void executeWithFileSetsWithDuplicatesAndAllowDuplicates() throws IOException {
try {
mojo.setAllowDuplicates(true);
mojo.setArgs(args);
mojo.setFileSets(getFileSetsWithDuplicates(rootDir));
mojo.execute();
} catch (final MojoExecutionException e) {
fail(e.getLongMessage());
}
// weirdly, Windows del command okay with duplicate files as args
// assertThat(contentOf(errorFile)).as("the errorFile's contents").matches(".*file4.*");
assertThat(contentOf(errorFile)).as("the errorFile is empty").hasSize(0);
assertDeepEmpty(rootDir);
}
@Test
public void executeWithFileSetsWithDuplicatesAndDisallowDuplicates() throws IOException,
MojoExecutionException {
mojo.setAllowDuplicates(false);
mojo.setArgs(args);
mojo.setFileSets(getFileSetsWithDuplicates(rootDir));
expected.expect(MojoExecutionException.class);
expected.expectMessage("a duplicate file");
expected.expectMessage("file4");
mojo.execute();
}
private List<FileSet> getFileSets(final Path rootDir) throws IOException {
final List<FileSet> fileSets = new ArrayList<>();
FileSet fileSet = new FileSet();
fileSet.setDirectory(rootDir.toString());
fileSet.addInclude("**/file?");
fileSet.addExclude("**/file4");
fileSets.add(fileSet);
fileSet = new FileSet();
fileSet.setDirectory(rootDir.toString());
fileSet.addInclude("**/*");
fileSet.addExclude("**/file?");
fileSets.add(fileSet);
fileSet = new FileSet();
fileSet.setDirectory(rootDir.toString());
fileSet.addInclude("**/file4");
fileSets.add(fileSet);
return fileSets;
}
private List<FileSet> getFileSetsWithNonExistentIncludes(final Path rootDir) throws IOException {
final List<FileSet> fileSets = getFileSets(rootDir);
final FileSet fileSet = new FileSet();
fileSet.setDirectory(rootDir.toString());
for (final String arg : BAD_ARGS) {
fileSet.addInclude(arg);
}
fileSets.add(fileSet);
return fileSets;
}
private List<FileSet> getFileSetsWithDuplicates(final Path rootDir) throws IOException {
final List<FileSet> fileSets = getFileSets(rootDir);
final FileSet fileSet = new FileSet();
fileSet.setDirectory(rootDir.toString());
fileSet.addInclude("**/file4");
fileSets.add(fileSet);
return fileSets;
}
private void addFileArgs(final Path rootDir, final String glob, final List<String> args)
throws IOException {
final PathMatcher pathMatcher = rootDir.getFileSystem().getPathMatcher("glob:" + glob);
Files.walkFileTree(rootDir, new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult visitFile(final Path file, final BasicFileAttributes attrs)
throws IOException {
if (pathMatcher.matches(file)) {
args.add(file.toRealPath().toString());
}
return super.visitFile(file, attrs);
}
});
}
private void addFileArgs(final Path rootDir, final List<String> files, final List<String> args) {
for (final String file : files) {
args.add(rootDir.resolve(file).toAbsolutePath().toString());
}
}
public static final Matcher<String> matchesRegex(final String regex) {
return new RegexMatcher(regex);
}
public static final class RegexMatcher extends TypeSafeMatcher<String> {
private final Pattern pattern;
private RegexMatcher(final String regex) {
pattern = Pattern.compile(Objects.requireNonNull(regex));
}
@Override
public void describeTo(final Description description) {
description.appendText("regex of '" + pattern.pattern() + "'");
}
@Override
protected boolean matchesSafely(final String item) {
return pattern.matcher(item).matches();
}
}
}