Preserve dependencies when uninstalling from the CLI

Previously, the CLI did not keep track of a dependency's users. This
meant that installing two extensions with a common dependency and
then unistalling one extension would break the other extension as the
common dependency would be deleted:

 1. Install a that depends on c
 2. Install b that depends on c
 3. Uninstall b
 4. a is now broken as c has been deleted

This commit updates the CLI to maintain a count for each artifact
that's installed into /lib. An artifact is now only deleted when the
count reaches zero.

As part of this change the code has been
extensively refactored to bring the structure into line with other CLI
commands and to improve testability.

Closes gh-1410
This commit is contained in:
Andy Wilkinson 2014-10-14 14:40:44 +01:00
parent 4e636f069f
commit a2c5b6a7bb
8 changed files with 697 additions and 194 deletions

View File

@ -25,6 +25,7 @@ import org.springframework.boot.cli.command.CommandFactory;
import org.springframework.boot.cli.command.core.VersionCommand;
import org.springframework.boot.cli.command.grab.GrabCommand;
import org.springframework.boot.cli.command.install.InstallCommand;
import org.springframework.boot.cli.command.install.UninstallCommand;
import org.springframework.boot.cli.command.jar.JarCommand;
import org.springframework.boot.cli.command.run.RunCommand;
import org.springframework.boot.cli.command.test.TestCommand;
@ -38,7 +39,7 @@ public class DefaultCommandFactory implements CommandFactory {
private static final List<Command> DEFAULT_COMMANDS = Arrays.<Command> asList(
new VersionCommand(), new RunCommand(), new TestCommand(), new GrabCommand(),
new JarCommand(), InstallCommand.install(), InstallCommand.uninstall());
new JarCommand(), new InstallCommand(), new UninstallCommand());
@Override
public Collection<Command> getCommands() {

View File

@ -0,0 +1,38 @@
/*
* Copyright 2012-2014 the original author or authors.
*
* Licensed 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.springframework.boot.cli.command.install;
import java.io.File;
import java.util.List;
/**
* @author Andy Wilkinson
* @since 1.2.0
*/
interface DependencyResolver {
/**
* Resolves the given {@code artifactIdentifiers}, typically in the form
* "group:artifact:version", and their dependencies.
*
* @param artifactIdentifiers The artifacts to resolve
* @return The {@code File}s for the resolved artifacts
* @throws Exception if dependency resolution fails
*/
List<File> resolve(List<String> artifactIdentifiers) throws Exception;
}

View File

@ -0,0 +1,99 @@
/*
* Copyright 2012-2014 the original author or authors.
*
* Licensed 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.springframework.boot.cli.command.install;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.codehaus.groovy.control.CompilationFailedException;
import org.springframework.boot.cli.compiler.GroovyCompiler;
import org.springframework.boot.cli.compiler.GroovyCompilerConfiguration;
/**
* A {@code DependencyResolver} implemented using Groovy's {@code @Grab}
*
* @author Dave Syer
* @author Andy Wilkinson
* @since 1.2.0
*/
class GroovyGrabDependencyResolver implements DependencyResolver {
private final GroovyCompilerConfiguration configuration;
public GroovyGrabDependencyResolver(GroovyCompilerConfiguration configuration) {
this.configuration = configuration;
}
@Override
public List<File> resolve(List<String> artifactIdentifiers)
throws CompilationFailedException, IOException {
GroovyCompiler groovyCompiler = new GroovyCompiler(this.configuration);
List<File> artifactFiles = new ArrayList<File>();
if (!artifactIdentifiers.isEmpty()) {
List<URL> initialUrls = getClassPathUrls(groovyCompiler);
groovyCompiler.compile(createSources(artifactIdentifiers));
List<URL> artifactUrls = getClassPathUrls(groovyCompiler);
artifactUrls.removeAll(initialUrls);
for (URL artifactUrl : artifactUrls) {
artifactFiles.add(toFile(artifactUrl));
}
}
return artifactFiles;
}
private List<URL> getClassPathUrls(GroovyCompiler compiler) {
return new ArrayList<URL>(Arrays.asList(compiler.getLoader().getURLs()));
}
private String createSources(List<String> artifactIdentifiers) throws IOException {
File file = File.createTempFile("SpringCLIDependency", ".groovy");
file.deleteOnExit();
OutputStreamWriter stream = new OutputStreamWriter(new FileOutputStream(file),
"UTF-8");
try {
for (String artifactIdentifier : artifactIdentifiers) {
stream.write("@Grab('" + artifactIdentifier + "')");
}
// Dummy class to force compiler to do grab
stream.write("class Installer {}");
}
finally {
stream.close();
}
// Windows paths get tricky unless you work with URI
return file.getAbsoluteFile().toURI().toString();
}
private File toFile(URL url) {
try {
return new File(url.toURI());
}
catch (URISyntaxException ex) {
return new File(url.getPath());
}
}
}

View File

@ -15,242 +15,59 @@
*/
package org.springframework.boot.cli.command.install;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import joptsimple.OptionSet;
import joptsimple.OptionSpec;
import org.springframework.boot.cli.command.Command;
import org.springframework.boot.cli.command.OptionParsingCommand;
import org.springframework.boot.cli.command.options.CompilerOptionHandler;
import org.springframework.boot.cli.command.options.OptionSetGroovyCompilerConfiguration;
import org.springframework.boot.cli.command.status.ExitStatus;
import org.springframework.boot.cli.compiler.GroovyCompiler;
import org.springframework.boot.cli.compiler.GroovyCompilerConfiguration;
import org.springframework.boot.cli.compiler.RepositoryConfigurationFactory;
import org.springframework.boot.cli.compiler.grape.RepositoryConfiguration;
import org.springframework.boot.cli.util.Log;
import org.springframework.util.FileCopyUtils;
import org.springframework.util.SystemPropertyUtils;
/**
* {@link Command} to install additional dependencies into the CLI.
*
* @author Dave Syer
* @author Andy Wilkinson
* @since 1.2.0
*/
public class InstallCommand extends OptionParsingCommand {
private final String usageHelp;
public static Command install() {
return new InstallCommand("install", "Install dependencies to lib directory",
"[options] <coordinates>", new InstallFileProcessorFactory());
}
public static Command uninstall() {
return new InstallCommand("uninstall",
"Uninstall dependencies from lib directory", "[options] <coordinates>",
new UninstallFileProcessorFactory());
}
private InstallCommand(String name, String description, String usageHelp,
FileProcessorFactory visitor) {
super(name, description, new InstallOptionHandler(visitor));
this.usageHelp = usageHelp;
public InstallCommand() {
super("install", "Install dependencies to the lib directory",
new InstallOptionHandler());
}
@Override
public String getUsageHelp() {
return this.usageHelp;
return "[options] <coordinates>";
}
private static final class InstallOptionHandler extends CompilerOptionHandler {
private FileProcessorFactory factory;
private OptionSpec<Void> allOption;
public InstallOptionHandler(FileProcessorFactory factory) {
this.factory = factory;
}
@Override
protected void doOptions() {
if (this.factory.getClass().getName().contains("UninstallFile")) {
this.allOption = option("all", "Uninstall all");
}
}
@Override
protected ExitStatus run(OptionSet options) throws Exception {
@SuppressWarnings("unchecked")
List<String> args = (List<String>) options.nonOptionArguments();
if (options.has(this.allOption)) {
if (!args.isEmpty()) {
throw new IllegalArgumentException(
"No non-optional arguments permitted with --all");
}
uninstallAllJars();
return ExitStatus.OK;
}
if (args.isEmpty()) {
throw new IllegalArgumentException(
"Non-optional arguments required. Specify dependencies via group:artifact:version");
"Please specify at least one dependency, in the form group:artifact:version, to install");
}
List<RepositoryConfiguration> repositoryConfiguration = RepositoryConfigurationFactory
.createDefaultRepositoryConfiguration();
GroovyCompilerConfiguration configuration = new OptionSetGroovyCompilerConfiguration(
options, this, repositoryConfiguration) {
@Override
public boolean isAutoconfigure() {
return false;
}
};
GroovyCompiler groovyCompiler = new GroovyCompiler(configuration);
try {
if (!args.isEmpty()) {
List<URL> initialUrls = getClassPathUrls(groovyCompiler);
groovyCompiler.compile(createSources(args));
List<URL> urlsToProcessor = getClassPathUrls(groovyCompiler);
urlsToProcessor.removeAll(initialUrls);
processJars(urlsToProcessor);
}
new Installer(options, this).install(args);
}
catch (Exception ex) {
String message = ex.getMessage();
Log.error(message != null ? message : ex.getClass().toString());
catch (Exception e) {
String message = e.getMessage();
Log.error(message != null ? message : e.getClass().toString());
}
return ExitStatus.OK;
}
private List<URL> getClassPathUrls(GroovyCompiler compiler) {
return new ArrayList<URL>(Arrays.asList(compiler.getLoader().getURLs()));
}
private void processJars(List<URL> urlsToProcess) throws IOException {
File lib = getDefaultLibDirectory();
FileProcessor processor = this.factory.processor(lib);
for (URL url : urlsToProcess) {
File file = toFile(url);
Log.info("Processing: " + file);
if (file.getName().endsWith(".jar")) {
processor.processFile(file);
}
}
}
private File toFile(URL url) {
try {
return new File(url.toURI());
}
catch (URISyntaxException ex) {
return new File(url.getPath());
}
}
private void uninstallAllJars() throws IOException {
File lib = getDefaultLibDirectory();
File[] filesInLib = lib.listFiles();
if (filesInLib != null) {
FileProcessor processor = new DeleteNotTheCliProcessor();
for (File file : filesInLib) {
processor.processFile(file);
}
}
}
private String createSources(List<String> args) throws IOException {
File file = File.createTempFile("SpringInstallCommand", ".groovy");
file.deleteOnExit();
OutputStreamWriter stream = new OutputStreamWriter(
new FileOutputStream(file), "UTF-8");
try {
for (String arg : args) {
stream.write("@Grab('" + arg + "')");
}
// Dummy class to force compiler to do grab
stream.write("class Installer {}");
}
finally {
stream.close();
}
// Windows paths get tricky unless you work with URI
return file.getAbsoluteFile().toURI().toString();
}
private static File getDefaultLibDirectory() {
String home = SystemPropertyUtils
.resolvePlaceholders("${spring.home:${SPRING_HOME:.}}");
final File lib = new File(home, "lib");
return lib;
}
}
private interface FileProcessorFactory {
FileProcessor processor(File lib);
}
private interface FileProcessor {
void processFile(File file) throws IOException;
}
private static class DeleteNotTheCliProcessor implements FileProcessor {
@Override
public void processFile(File file) throws IOException {
if (!file.getName().startsWith("spring-boot-cli")) {
file.delete();
}
}
}
private static class InstallFileProcessorFactory implements FileProcessorFactory {
@Override
public FileProcessor processor(final File lib) {
Log.info("Installing into: " + lib);
lib.mkdirs();
return new FileProcessor() {
@Override
public void processFile(File file) throws IOException {
FileCopyUtils.copy(file, new File(lib, file.getName()));
}
};
}
}
private static class UninstallFileProcessorFactory implements FileProcessorFactory {
@Override
public FileProcessor processor(final File lib) {
Log.info("Uninstalling from: " + lib);
return new FileProcessor() {
@Override
public void processFile(File file) throws IOException {
new File(lib, file.getName()).delete();
}
};
}
}
}

View File

@ -0,0 +1,172 @@
/*
* Copyright 2012-2014 the original author or authors.
*
* Licensed 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.springframework.boot.cli.command.install;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.List;
import java.util.Properties;
import joptsimple.OptionSet;
import org.springframework.boot.cli.command.options.CompilerOptionHandler;
import org.springframework.boot.cli.command.options.OptionSetGroovyCompilerConfiguration;
import org.springframework.boot.cli.compiler.GroovyCompilerConfiguration;
import org.springframework.boot.cli.compiler.RepositoryConfigurationFactory;
import org.springframework.boot.cli.compiler.grape.RepositoryConfiguration;
import org.springframework.boot.cli.util.Log;
import org.springframework.util.FileCopyUtils;
import org.springframework.util.SystemPropertyUtils;
/**
* @author Andy Wilkinson
* @since 1.2.0
*/
class Installer {
private final DependencyResolver dependencyResolver;
private final Properties installCounts;
Installer(OptionSet options, CompilerOptionHandler compilerOptionHandler)
throws IOException {
this(new GroovyGrabDependencyResolver(createCompilerConfiguration(options,
compilerOptionHandler)));
}
Installer(DependencyResolver resolver) throws IOException {
this.dependencyResolver = resolver;
this.installCounts = loadInstallCounts();
}
private static GroovyCompilerConfiguration createCompilerConfiguration(
OptionSet options, CompilerOptionHandler compilerOptionHandler) {
List<RepositoryConfiguration> repositoryConfiguration = RepositoryConfigurationFactory
.createDefaultRepositoryConfiguration();
return new OptionSetGroovyCompilerConfiguration(options, compilerOptionHandler,
repositoryConfiguration) {
@Override
public boolean isAutoconfigure() {
return false;
}
};
}
private static Properties loadInstallCounts() throws IOException {
Properties properties = new Properties();
File installed = getInstalled();
if (installed.exists()) {
FileReader reader = new FileReader(installed);
properties.load(reader);
reader.close();
}
return properties;
}
private void saveInstallCounts() throws IOException {
FileWriter writer = new FileWriter(getInstalled());
try {
this.installCounts.store(writer, null);
}
finally {
writer.close();
}
}
public void install(List<String> artifactIdentifiers) throws Exception {
File libDirectory = getDefaultLibDirectory();
libDirectory.mkdirs();
Log.info("Installing into: " + libDirectory);
List<File> artifactFiles = this.dependencyResolver.resolve(artifactIdentifiers);
for (File artifactFile : artifactFiles) {
int installCount = getInstallCount(artifactFile);
if (installCount == 0) {
FileCopyUtils.copy(artifactFile,
new File(libDirectory, artifactFile.getName()));
}
setInstallCount(artifactFile, installCount + 1);
}
saveInstallCounts();
}
private int getInstallCount(File file) {
String countString = this.installCounts.getProperty(file.getName());
if (countString == null) {
return 0;
}
return Integer.valueOf(countString);
}
private void setInstallCount(File file, int count) {
if (count == 0) {
this.installCounts.remove(file.getName());
}
else {
this.installCounts.setProperty(file.getName(), Integer.toString(count));
}
}
public void uninstall(List<String> artifactIdentifiers) throws Exception {
File libDirectory = getDefaultLibDirectory();
Log.info("Uninstalling from: " + libDirectory);
List<File> artifactFiles = this.dependencyResolver.resolve(artifactIdentifiers);
for (File artifactFile : artifactFiles) {
int installCount = getInstallCount(artifactFile);
if (installCount <= 1) {
new File(libDirectory, artifactFile.getName()).delete();
}
setInstallCount(artifactFile, installCount - 1);
}
saveInstallCounts();
}
public void uninstallAll() throws Exception {
File libDirectory = getDefaultLibDirectory();
Log.info("Uninstalling from: " + libDirectory);
for (String name : this.installCounts.stringPropertyNames()) {
new File(libDirectory, name).delete();
}
this.installCounts.clear();
saveInstallCounts();
}
private static File getDefaultLibDirectory() {
String home = SystemPropertyUtils
.resolvePlaceholders("${spring.home:${SPRING_HOME:.}}");
final File lib = new File(home, "lib");
return lib;
}
private static File getInstalled() {
return new File(getDefaultLibDirectory(), ".installed");
}
}

View File

@ -0,0 +1,91 @@
/*
* Copyright 2012-2014 the original author or authors.
*
* Licensed 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.springframework.boot.cli.command.install;
import java.util.List;
import joptsimple.OptionSet;
import joptsimple.OptionSpec;
import org.springframework.boot.cli.command.Command;
import org.springframework.boot.cli.command.OptionParsingCommand;
import org.springframework.boot.cli.command.options.CompilerOptionHandler;
import org.springframework.boot.cli.command.status.ExitStatus;
import org.springframework.boot.cli.util.Log;
/**
* {@link Command} to uninstall dependencies from the CLI's lib directory
*
* @author Dave Syer
* @author Andy Wilkinson
* @since 1.2.0
*/
public class UninstallCommand extends OptionParsingCommand {
public UninstallCommand() {
super("uninstall", "Uninstall dependencies from the lib directory",
new UninstallOptionHandler());
}
@Override
public String getUsageHelp() {
return "[options] <coordinates>";
}
private static class UninstallOptionHandler extends CompilerOptionHandler {
private OptionSpec<Void> allOption;
@Override
protected void doOptions() {
this.allOption = option("all", "Uninstall all");
}
@Override
protected ExitStatus run(OptionSet options) throws Exception {
@SuppressWarnings("unchecked")
List<String> args = (List<String>) options.nonOptionArguments();
try {
if (options.has(this.allOption)) {
if (!args.isEmpty()) {
throw new IllegalArgumentException(
"Please use --all without specifying any dependencies");
}
new Installer(options, this).uninstallAll();
}
if (args.isEmpty()) {
throw new IllegalArgumentException(
"Please specify at least one dependency, in the form group:artifact:version, to uninstall");
}
new Installer(options, this).uninstall(args);
}
catch (Exception e) {
String message = e.getMessage();
Log.error(message != null ? message : e.getClass().toString());
}
return ExitStatus.OK;
}
}
}

View File

@ -0,0 +1,130 @@
/*
* Copyright 2012-2014 the original author or authors.
*
* Licensed 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.springframework.boot.cli.command.install;
import java.io.File;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.junit.Before;
import org.junit.Test;
import org.springframework.boot.cli.compiler.GroovyCompilerConfiguration;
import org.springframework.boot.cli.compiler.GroovyCompilerScope;
import org.springframework.boot.cli.compiler.RepositoryConfigurationFactory;
import org.springframework.boot.cli.compiler.grape.RepositoryConfiguration;
import static org.hamcrest.Matchers.hasItems;
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.startsWith;
import static org.junit.Assert.assertThat;
/**
* Tests for {@link GroovyGrabDependencyResolver}.
*
* @author Andy Wilkinson
*/
public class GroovyGrabDependencyResolverTests {
private DependencyResolver resolver;
@Before
public void setupResolver() {
GroovyCompilerConfiguration configuration = new GroovyCompilerConfiguration() {
@Override
public boolean isGuessImports() {
return true;
}
@Override
public boolean isGuessDependencies() {
return true;
}
@Override
public boolean isAutoconfigure() {
return false;
}
@Override
public GroovyCompilerScope getScope() {
return GroovyCompilerScope.DEFAULT;
}
@Override
public List<RepositoryConfiguration> getRepositoryConfiguration() {
return RepositoryConfigurationFactory
.createDefaultRepositoryConfiguration();
}
@Override
public String[] getClasspath() {
return new String[] { "." };
}
};
this.resolver = new GroovyGrabDependencyResolver(configuration);
}
@Test
public void resolveArtifactWithNoDependencies() throws Exception {
List<File> resolved = this.resolver.resolve(Arrays
.asList("commons-logging:commons-logging:1.1.3"));
assertThat(resolved, hasSize(1));
assertThat(getNames(resolved), hasItems("commons-logging-1.1.3.jar"));
}
@Test
public void resolveArtifactWithDependencies() throws Exception {
List<File> resolved = this.resolver.resolve(Arrays
.asList("org.springframework:spring-core:4.1.1.RELEASE"));
assertThat(resolved, hasSize(2));
assertThat(getNames(resolved),
hasItems("commons-logging-1.1.3.jar", "spring-core-4.1.1.RELEASE.jar"));
}
@SuppressWarnings("unchecked")
@Test
public void resolveShorthandArtifactWithDependencies() throws Exception {
List<File> resolved = this.resolver.resolve(Arrays.asList("spring-core"));
assertThat(resolved, hasSize(2));
assertThat(getNames(resolved),
hasItems(startsWith("commons-logging-"), startsWith("spring-core-")));
}
@Test
public void resolveMultipleArtifacts() throws Exception {
List<File> resolved = this.resolver.resolve(Arrays.asList("junit:junit:4.11",
"commons-logging:commons-logging:1.1.3"));
assertThat(resolved, hasSize(3));
assertThat(
getNames(resolved),
hasItems("junit-4.11.jar", "commons-logging-1.1.3.jar",
"hamcrest-core-1.3.jar"));
}
public Set<String> getNames(Collection<File> files) {
Set<String> names = new HashSet<String>(files.size());
for (File file : files) {
names.add(file.getName());
}
return names;
}
}

View File

@ -0,0 +1,155 @@
/*
* Copyright 2012-2014 the original author or authors.
*
* Licensed 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.springframework.boot.cli.command.install;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.springframework.util.FileSystemUtils;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.junit.Assert.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
/**
* Tests for {@link Installer}
*
* @author Andy Wilkinson
*/
public class InstallerTests {
public DependencyResolver resolver = mock(DependencyResolver.class);
@Rule
public TemporaryFolder tempFolder = new TemporaryFolder();
private Installer installer;
@Before
public void setUp() throws IOException {
System.setProperty("spring.home", "target");
FileSystemUtils.deleteRecursively(new File("target/lib"));
this.installer = new Installer(this.resolver);
}
@After
public void cleanUp() {
System.clearProperty("spring.home");
}
@Test
public void installNewDependency() throws Exception {
File foo = createTemporaryFile("foo.jar");
when(this.resolver.resolve(Arrays.asList("foo"))).thenReturn(Arrays.asList(foo));
this.installer.install(Arrays.asList("foo"));
assertThat(getNamesOfFilesInLib(), containsInAnyOrder("foo.jar", ".installed"));
}
@Test
public void installAndUninstall() throws Exception {
File foo = createTemporaryFile("foo.jar");
when(this.resolver.resolve(Arrays.asList("foo"))).thenReturn(Arrays.asList(foo));
this.installer.install(Arrays.asList("foo"));
this.installer.uninstall(Arrays.asList("foo"));
assertThat(getNamesOfFilesInLib(), contains(".installed"));
}
@Test
public void installAndUninstallWithCommonDependencies() throws Exception {
File alpha = createTemporaryFile("alpha.jar");
File bravo = createTemporaryFile("bravo.jar");
File charlie = createTemporaryFile("charlie.jar");
when(this.resolver.resolve(Arrays.asList("bravo"))).thenReturn(
Arrays.asList(bravo, alpha));
when(this.resolver.resolve(Arrays.asList("charlie"))).thenReturn(
Arrays.asList(charlie, alpha));
this.installer.install(Arrays.asList("bravo"));
assertThat(getNamesOfFilesInLib(),
containsInAnyOrder("alpha.jar", "bravo.jar", ".installed"));
this.installer.install(Arrays.asList("charlie"));
assertThat(getNamesOfFilesInLib(),
containsInAnyOrder("alpha.jar", "bravo.jar", "charlie.jar", ".installed"));
this.installer.uninstall(Arrays.asList("bravo"));
assertThat(getNamesOfFilesInLib(),
containsInAnyOrder("alpha.jar", "charlie.jar", ".installed"));
this.installer.uninstall(Arrays.asList("charlie"));
assertThat(getNamesOfFilesInLib(), containsInAnyOrder(".installed"));
}
@Test
public void uninstallAll() throws Exception {
File alpha = createTemporaryFile("alpha.jar");
File bravo = createTemporaryFile("bravo.jar");
File charlie = createTemporaryFile("charlie.jar");
when(this.resolver.resolve(Arrays.asList("bravo"))).thenReturn(
Arrays.asList(bravo, alpha));
when(this.resolver.resolve(Arrays.asList("charlie"))).thenReturn(
Arrays.asList(charlie, alpha));
this.installer.install(Arrays.asList("bravo"));
this.installer.install(Arrays.asList("charlie"));
assertThat(getNamesOfFilesInLib(),
containsInAnyOrder("alpha.jar", "bravo.jar", "charlie.jar", ".installed"));
this.installer.uninstallAll();
assertThat(getNamesOfFilesInLib(), containsInAnyOrder(".installed"));
}
private Set<String> getNamesOfFilesInLib() {
Set<String> names = new HashSet<String>();
for (File file : new File("target/lib").listFiles()) {
names.add(file.getName());
}
return names;
}
private File createTemporaryFile(String name) throws IOException {
File temporaryFile = this.tempFolder.newFile(name);
temporaryFile.deleteOnExit();
return temporaryFile;
}
}