From b17cd6377a7db349b435251165221efe4441b35f Mon Sep 17 00:00:00 2001 From: Hector Oswaldo Caballero Date: Mon, 15 Jun 2015 16:02:45 -0400 Subject: [PATCH] Add acceptance (IT) tests support for plugins Create PluginDaemonTest to load plugin jars in the Gerrit test server before running the plugin acceptance tests. Plugin IT classes shall extend PluginDaemonTest in order to enable this functionality. Make beforeTest in AbstractDaemonTest protected so it can be overridden by hereby PluginDaemonTest. Add UNIT_TEST_GERRIT_SITE constant to InMemoryTestingDatabaseModule so it can be reused by PluginDaemonTest. Package acceptance tests into plugin-api jar. Support standalone and non-standalone (all) buck modes for tested plugin. Running IT tests using a maven pom is *not* supported by this change. Support ability for such plugin IT tests to set plugin config string in gerrit config. Change-Id: Ie9e63de622708272f4f5e154a80a55a97d8ff77c --- gerrit-acceptance-tests/BUCK | 1 + .../gerrit/acceptance/AbstractDaemonTest.java | 2 +- .../InMemoryTestingDatabaseModule.java | 5 +- .../gerrit/acceptance/PluginDaemonTest.java | 234 ++++++++++++++++++ gerrit-plugin-api/BUCK | 1 + 5 files changed, 241 insertions(+), 2 deletions(-) create mode 100644 gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/PluginDaemonTest.java diff --git a/gerrit-acceptance-tests/BUCK b/gerrit-acceptance-tests/BUCK index 833070b7dd..3adab73fdc 100644 --- a/gerrit-acceptance-tests/BUCK +++ b/gerrit-acceptance-tests/BUCK @@ -44,6 +44,7 @@ java_library( '//lib/mina:sshd', ], visibility = [ + '//gerrit-plugin-api/...', '//tools/eclipse:classpath', '//gerrit-acceptance-tests/...', ], diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/AbstractDaemonTest.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/AbstractDaemonTest.java index 18ee2ef8e8..cc547f9eb8 100644 --- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/AbstractDaemonTest.java +++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/AbstractDaemonTest.java @@ -222,7 +222,7 @@ public abstract class AbstractDaemonTest { return cfg.getBoolean("change", null, "submitWholeTopic", false); } - private void beforeTest(Description description) throws Exception { + protected void beforeTest(Description description) throws Exception { GerritServer.Description classDesc = GerritServer.Description.forTestClass(description, configName); GerritServer.Description methodDesc = diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/InMemoryTestingDatabaseModule.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/InMemoryTestingDatabaseModule.java index 8f4c2d41db..02f2ec686d 100644 --- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/InMemoryTestingDatabaseModule.java +++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/InMemoryTestingDatabaseModule.java @@ -47,6 +47,9 @@ import java.nio.file.Path; import java.nio.file.Paths; class InMemoryTestingDatabaseModule extends LifecycleModule { + + static final String UNIT_TEST_GERRIT_SITE = "UNIT_TEST_GERRIT_SITE"; + private final Config cfg; InMemoryTestingDatabaseModule(Config cfg) { @@ -62,7 +65,7 @@ class InMemoryTestingDatabaseModule extends LifecycleModule { // TODO(dborowitz): Use jimfs. bind(Path.class) .annotatedWith(SitePath.class) - .toInstance(Paths.get("UNIT_TEST_GERRIT_SITE")); + .toInstance(Paths.get(UNIT_TEST_GERRIT_SITE)); bind(GitRepositoryManager.class) .toInstance(new InMemoryRepositoryManager()); diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/PluginDaemonTest.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/PluginDaemonTest.java new file mode 100644 index 0000000000..60e9f90c1f --- /dev/null +++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/PluginDaemonTest.java @@ -0,0 +1,234 @@ +// Copyright (C) 2015 The Android Open Source Project +// +// 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 com.google.gerrit.acceptance; + +import com.google.common.base.MoreObjects; +import com.google.common.base.Strings; +import com.google.gerrit.server.config.SitePaths; + +import org.eclipse.jgit.errors.ConfigInvalidException; +import org.eclipse.jgit.storage.file.FileBasedConfig; +import org.eclipse.jgit.util.FS; +import org.junit.AfterClass; +import org.junit.runner.Description; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.nio.file.FileVisitResult; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.StandardCopyOption; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.Properties; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public abstract class PluginDaemonTest extends AbstractDaemonTest { + + private static final String BUCKLC = "buck"; + private static final String BUCKOUT = "buck-out"; + private static final String BUILD = "build"; + private static final String EMPTY = "src/main/java/ForceJarIfMissing.java"; + private static final Path testSite = + Paths.get(InMemoryTestingDatabaseModule.UNIT_TEST_GERRIT_SITE); + + private Path gen; + private Path pluginRoot; + private Path pluginsSitePath; + private Path pluginSubPath; + private Path pluginSource; + private String pluginName; + private boolean standalone; + + @Override + protected void beforeTest(Description description) throws Exception { + locatePaths(); + retrievePluginName(); + buildPluginJar(); + createTestSite(); + copyJarToTestSite(); + super.beforeTest(description); + } + + @AfterClass + public static void removeTestSite() throws IOException { + if (!Files.exists(testSite)) { + return; + } + Files.walkFileTree(testSite, new SimpleFileVisitor() { + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) + throws IOException { + Files.deleteIfExists(file); + return FileVisitResult.CONTINUE; + } + + @Override + public FileVisitResult postVisitDirectory(Path dir, IOException exc) + throws IOException { + Files.deleteIfExists(dir); + return FileVisitResult.CONTINUE; + } + }); + } + + protected void setPluginConfigString(String name, String value) + throws IOException, ConfigInvalidException { + SitePaths sitePath = new SitePaths(testSite); + FileBasedConfig cfg = getGerritConfigFile(sitePath); + cfg.load(); + cfg.setString("plugin", pluginName, name, value); + cfg.save(); + } + + private FileBasedConfig getGerritConfigFile(SitePaths sitePath) + throws IOException { + FileBasedConfig cfg = + new FileBasedConfig(sitePath.gerrit_config.toFile(), FS.DETECTED); + if (!cfg.getFile().exists()) { + Path etc_path = Files.createDirectories(sitePath.etc_dir); + Files.createFile(etc_path.resolve("gerrit.config")); + } + return cfg; + } + + private void locatePaths() { + URL pluginClassesUrl = + getClass().getProtectionDomain().getCodeSource().getLocation(); + Path basePath = Paths.get(pluginClassesUrl.getPath()).getParent(); + + int idx = 0; + int buckOutIdx = 0; + int pluginsIdx = 0; + for (Path subPath : basePath) { + if (subPath.endsWith("plugins")) { + pluginsIdx = idx; + } + if (subPath.endsWith(BUCKOUT)) { + buckOutIdx = idx; + } + idx++; + } + standalone = checkStandalone(basePath); + pluginRoot = basePath.getRoot().resolve(basePath.subpath(0, buckOutIdx)); + gen = pluginRoot.resolve(BUCKOUT).resolve("gen"); + + if (standalone) { + pluginSource = pluginRoot; + } else { + pluginSubPath = basePath.subpath(pluginsIdx, pluginsIdx + 2); + pluginSource = pluginRoot.resolve(pluginSubPath); + } + } + + private boolean checkStandalone(Path basePath) { + String pathCharStringOrNone = "[a-zA-Z0-9._-]*?"; + Pattern pattern = Pattern.compile(pathCharStringOrNone + "gerrit" + + pathCharStringOrNone); + Path partialPath = basePath; + for (int i = basePath.getNameCount(); i > 0; i--) { + int count = partialPath.getNameCount(); + if (count > 1) { + String gerritDirCandidate = + partialPath.subpath(count - 2, count - 1).toString(); + if (pattern.matcher(gerritDirCandidate).matches()) { + if (partialPath.endsWith(gerritDirCandidate + "/" + BUCKOUT)) { + return false; + } + } + } + partialPath = partialPath.getParent(); + } + return true; + } + + private void retrievePluginName() throws IOException { + Path buckFile = pluginSource.resolve("BUCK"); + byte[] bytes = Files.readAllBytes(buckFile); + String buckContent = + new String(bytes, StandardCharsets.UTF_8).replaceAll("\\s+", ""); + Matcher matcher = + Pattern.compile("gerrit_plugin\\(name='(.*?)'").matcher(buckContent); + if (matcher.find()) { + pluginName = matcher.group(1); + } + if (Strings.isNullOrEmpty(pluginName)) { + if (standalone) { + pluginName = pluginRoot.getFileName().toString(); + } else { + pluginName = pluginSubPath.getFileName().toString(); + } + } + } + + private void buildPluginJar() throws IOException, InterruptedException { + Properties properties = loadBuckProperties(); + String buck = + MoreObjects.firstNonNull(properties.getProperty(BUCKLC), BUCKLC); + String target; + if (standalone) { + target = "//:" + pluginName; + } else { + target = pluginSubPath.toString(); + } + + ProcessBuilder processBuilder = + new ProcessBuilder(buck, BUILD, target).directory(pluginRoot.toFile()) + .redirectErrorStream(true); + + Path forceJar = pluginSource.resolve(EMPTY); + Files.createFile(forceJar); + try { + processBuilder.start().waitFor(); + } finally { + Files.delete(forceJar); + // otherwise jar not made next time if missing again: + processBuilder.start().waitFor(); + } + } + + private Properties loadBuckProperties() throws IOException { + Properties properties = new Properties(); + Path propertiesPath = gen.resolve("tools").resolve("buck.properties"); + if (Files.exists(propertiesPath)) { + try (InputStream in = Files.newInputStream(propertiesPath)) { + properties.load(in); + } + } + return properties; + } + + private void createTestSite() throws IOException { + SitePaths sitePath = new SitePaths(testSite); + pluginsSitePath = Files.createDirectories(sitePath.plugins_dir); + Files.createDirectories(sitePath.tmp_dir); + } + + private void copyJarToTestSite() throws IOException { + Path pluginOut; + if (standalone) { + pluginOut = gen; + } else { + pluginOut = gen.resolve(pluginSubPath); + } + Path jar = pluginOut.resolve(pluginName + ".jar"); + Path dest = pluginsSitePath.resolve(jar.getFileName()); + Files.copy(jar, dest, StandardCopyOption.REPLACE_EXISTING); + } +} diff --git a/gerrit-plugin-api/BUCK b/gerrit-plugin-api/BUCK index 6a4e4c0704..ed11e0f075 100644 --- a/gerrit-plugin-api/BUCK +++ b/gerrit-plugin-api/BUCK @@ -20,6 +20,7 @@ java_binary( java_library( name = 'lib', exported_deps = PLUGIN_API + [ + '//gerrit-acceptance-tests:lib', '//gerrit-antlr:query_exception', '//gerrit-antlr:query_parser', '//gerrit-common:annotations',