/* * JBoss, Home of Professional Open Source. * Copyright 2016, Red Hat, Inc., and individual contributors * as indicated by the @author tags. See the copyright.txt file in the * distribution for a full listing of individual contributors. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.jboss.as.test.integration.management.cli; import org.jboss.as.cli.CommandContext; import org.jboss.as.test.integration.management.util.CLIWrapper; import org.jboss.as.test.shared.TestSuiteEnvironment; import org.jboss.shrinkwrap.api.ShrinkWrap; import org.jboss.shrinkwrap.api.spec.JavaArchive; import org.jboss.shrinkwrap.impl.base.exporter.zip.ZipExporterImpl; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import org.wildfly.core.testrunner.WildflyTestRunner; import java.io.File; import java.io.IOException; import java.nio.file.FileVisitOption; import java.nio.file.Files; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; import java.util.stream.Stream; import static org.junit.Assert.assertEquals; @RunWith(WildflyTestRunner.class) public class ModuleOpsCompletionTestCase { private static final String MODULE_NAME = "org.jboss.test.cli.climoduletest"; private static CLIWrapper cli; private static File jarFile; @BeforeClass public static void before() throws Exception { cleanUp(); cli = new CLIWrapper(true, null, System.in); final JavaArchive jar = ShrinkWrap.create(JavaArchive.class, "Dummy.jar"); jar.addClass(ModuleTestCase.class); jarFile = new File(TestSuiteEnvironment.getTmpDir() + File.separator + "Dummy.jar"); new ZipExporterImpl(jar).exportTo(jarFile, true); } @AfterClass public static void after() throws Exception { cli.close(); cleanUp(); Files.deleteIfExists(jarFile.toPath()); } private static void cleanUp() throws IOException { for (File dir : getModulePath().listFiles(f -> !f.getName().equals("system"))) { deleteRecursively(dir); } } @Test public void testModuleAddCompletionSuggestions() throws Exception { final CommandContext ctx = cli.getCommandContext(); final Stream<String> allTopLevelDirs = listTopLevelModuleDirs().map(File::getName).distinct().sorted(); // name on add operation should suggest all possible folders (not only valid module names) testSuggestion(ctx, allTopLevelDirs, "module add --name=", false); testSuggestion(ctx, Arrays.asList("org"), "module add --name=o", false); testSuggestion(ctx, Arrays.asList("org", "org."), "module add --name=org", false); try { // suggest folder without module descriptor new File(getModulePath(), "foo").mkdir(); testSuggestion(ctx, Arrays.asList("foo"), "module add --name=f", false); } finally { new File(getModulePath(), "foo").delete(); } } @Test public void testModuleRemoveCompletionSuggestions() throws Exception { final CommandContext ctx = cli.getCommandContext(); // name on remove operation should only suggest valid module names from the modules root directory try { testAdd("main"); testSuggestion(ctx, Arrays.asList("org."), "module remove --name=", false); testSuggestion(ctx, Arrays.asList("org."), "module remove --name=org", false); } finally { testRemove("main"); } } @Test public void testModuleDependenciesCompletionSuggestions() throws Exception { final CommandContext ctx = cli.getCommandContext(); final Stream<String> topLevelDirs = listTopLevelModuleDirs() .filter(this::isModuleTree) .map(f->f.getName() + ".") .distinct().sorted(); // dependencies should suggest all possible modules testSuggestion(ctx, topLevelDirs, "module add --name=foo --dependencies=", true); // completes started folder names testSuggestion(ctx, Arrays.asList("org."), "module add --name=foo --dependencies=o", true); testSuggestion(ctx, Arrays.asList("org."), "module add --name=foo --dependencies=bar,o", true); } @Test public void testExportModuleDependenciesCompletionSuggestions() throws Exception { final CommandContext ctx = cli.getCommandContext(); final Stream<String> topLevelDirs = listTopLevelModuleDirs() .filter(this::isModuleTree) .map(f -> f.getName() + ".") .distinct().sorted(); // export-dependencies should suggest all possible modules testSuggestion(ctx, topLevelDirs, "module add --name=foo --export-dependencies=", true); // completes started folder names testSuggestion(ctx, Arrays.asList("org."), "module add --name=foo --export-dependencies=o", true); testSuggestion(ctx, Arrays.asList("org."), "module add --name=foo --export-dependencies=bar,o", true); } private void testAdd(String slotName) throws Exception { // create a module cli.sendLine("module add --name=" + MODULE_NAME + ("main".equals(slotName) ? "" : " --slot=" + slotName) + " --resources=" + jarFile.getAbsolutePath() ); } private void testRemove(String slotName) throws Exception { // remove the module cli.sendLine("module remove --name=" + MODULE_NAME + ("main".equals(slotName) ? "" : " --slot=" + slotName) ); } private Stream<File> listTopLevelModuleDirs() { ArrayList<File> res = new ArrayList<>(); res.addAll(Arrays.asList(getModulePath().listFiles(f -> !f.getName().equals("system")))); if (new File(getModulePath(), "system/layers/").exists() ) { for (File layer : new File(getModulePath(), "system/layers/").listFiles()) { res.addAll(Arrays.asList(layer.listFiles())); } } if (new File(getModulePath(), "system/add-ons/").exists() ) { for (File layer : new File(getModulePath(), "system/add-ons/").listFiles()) { res.addAll(Arrays.asList(layer.listFiles())); } } return res.stream().sorted(); } private boolean isModuleTree(File f) { try { return Files.find(f.toPath(), Integer.MAX_VALUE, (p, attr)->p.getFileName().toString().equals("module.xml"), FileVisitOption.FOLLOW_LINKS).count() > 0; } catch (IOException e) { return false; } } private void testSuggestion(CommandContext ctx, Stream<String> expected, String buffer, boolean allowMultipleValues) { testSuggestion(ctx, expected.collect(Collectors.toList()), buffer, allowMultipleValues); } private void testSuggestion(CommandContext ctx, List<String> expected, String buffer, boolean allowMultipleValues) { List<String> candidates = new ArrayList<>(); final int offset = ctx.getDefaultCommandCompleter().complete(ctx, buffer, buffer.length(), candidates); assertEquals(expected, candidates); final int expectedIndex; if (allowMultipleValues && buffer.lastIndexOf('=') < buffer.lastIndexOf(',')) { expectedIndex = buffer.lastIndexOf(',') + 1; } else { expectedIndex = buffer.lastIndexOf('=') + 1; } assertEquals(expectedIndex, offset); } private static File getModulePath() { String modulePath = TestSuiteEnvironment.getSystemProperty("module.path", null); if (modulePath == null) { String jbossHome = TestSuiteEnvironment.getSystemProperty("jboss.dist", null); if (jbossHome == null) { throw new IllegalStateException( "Neither -Dmodule.path nor -Djboss.home were set"); } modulePath = jbossHome + File.separatorChar + "modules"; } else { modulePath = modulePath.split(File.pathSeparator)[0]; } File moduleDir = new File(modulePath); if (!moduleDir.exists()) { throw new IllegalStateException( "Determined module path does not exist"); } if (!moduleDir.isDirectory()) { throw new IllegalStateException( "Determined module path is not a dir"); } return moduleDir; } private static void deleteRecursively(final File dir) { if (dir.isDirectory()) { final File[] files = dir.listFiles(); for (File file : files) { if (file.isDirectory()) { deleteRecursively(file); } file.delete(); } } } }