summaryrefslogtreecommitdiffstats
path: root/java/com/google/gerrit/acceptance/GerritServer.java
diff options
context:
space:
mode:
Diffstat (limited to 'java/com/google/gerrit/acceptance/GerritServer.java')
-rw-r--r--java/com/google/gerrit/acceptance/GerritServer.java676
1 files changed, 676 insertions, 0 deletions
diff --git a/java/com/google/gerrit/acceptance/GerritServer.java b/java/com/google/gerrit/acceptance/GerritServer.java
new file mode 100644
index 0000000000..050de49003
--- /dev/null
+++ b/java/com/google/gerrit/acceptance/GerritServer.java
@@ -0,0 +1,676 @@
+// Copyright (C) 2013 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 static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkState;
+import static java.util.Objects.requireNonNull;
+import static org.apache.log4j.Logger.getLogger;
+
+import com.google.auto.value.AutoValue;
+import com.google.common.base.MoreObjects;
+import com.google.common.base.Strings;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.gerrit.acceptance.testsuite.account.AccountOperations;
+import com.google.gerrit.acceptance.testsuite.account.AccountOperationsImpl;
+import com.google.gerrit.acceptance.testsuite.group.GroupOperations;
+import com.google.gerrit.acceptance.testsuite.group.GroupOperationsImpl;
+import com.google.gerrit.common.Nullable;
+import com.google.gerrit.extensions.config.FactoryModule;
+import com.google.gerrit.lucene.LuceneIndexModule;
+import com.google.gerrit.pgm.Daemon;
+import com.google.gerrit.pgm.Init;
+import com.google.gerrit.server.config.GerritRuntime;
+import com.google.gerrit.server.config.GerritServerConfig;
+import com.google.gerrit.server.config.SitePath;
+import com.google.gerrit.server.git.receive.AsyncReceiveCommits;
+import com.google.gerrit.server.ssh.NoSshModule;
+import com.google.gerrit.server.util.ManualRequestContext;
+import com.google.gerrit.server.util.OneOffRequestContext;
+import com.google.gerrit.server.util.SocketUtil;
+import com.google.gerrit.server.util.SystemLog;
+import com.google.gerrit.testing.FakeEmailSender;
+import com.google.gerrit.testing.FakeGroupAuditService;
+import com.google.gerrit.testing.InMemoryDatabase;
+import com.google.gerrit.testing.InMemoryRepositoryManager;
+import com.google.gerrit.testing.NoteDbChecker;
+import com.google.gerrit.testing.NoteDbMode;
+import com.google.gerrit.testing.SshMode;
+import com.google.gerrit.testing.TempFileUtil;
+import com.google.inject.AbstractModule;
+import com.google.inject.Injector;
+import com.google.inject.Key;
+import com.google.inject.Module;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Field;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.URI;
+import java.nio.file.Path;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+import java.util.concurrent.BrokenBarrierException;
+import java.util.concurrent.CyclicBarrier;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Stream;
+import org.apache.log4j.ConsoleAppender;
+import org.apache.log4j.Level;
+import org.apache.log4j.LogManager;
+import org.apache.log4j.Logger;
+import org.apache.log4j.PatternLayout;
+import org.eclipse.jgit.lib.Config;
+import org.eclipse.jgit.lib.RepositoryCache;
+import org.eclipse.jgit.util.FS;
+
+public class GerritServer implements AutoCloseable {
+ public static class StartupException extends Exception {
+ private static final long serialVersionUID = 1L;
+
+ StartupException(String msg, Throwable cause) {
+ super(msg, cause);
+ }
+ }
+
+ @AutoValue
+ public abstract static class Description {
+ public static Description forTestClass(
+ org.junit.runner.Description testDesc, String configName) {
+ return new AutoValue_GerritServer_Description(
+ testDesc,
+ configName,
+ !has(UseLocalDisk.class, testDesc.getTestClass()) && !forceLocalDisk(),
+ !has(NoHttpd.class, testDesc.getTestClass()),
+ has(Sandboxed.class, testDesc.getTestClass()),
+ has(UseSsh.class, testDesc.getTestClass()),
+ null, // @GerritConfig is only valid on methods.
+ null, // @GerritConfigs is only valid on methods.
+ null, // @GlobalPluginConfig is only valid on methods.
+ null, // @GlobalPluginConfigs is only valid on methods.
+ getLogLevelThresholdAnnotation(testDesc));
+ }
+
+ public static Description forTestMethod(
+ org.junit.runner.Description testDesc, String configName) {
+ return new AutoValue_GerritServer_Description(
+ testDesc,
+ configName,
+ (testDesc.getAnnotation(UseLocalDisk.class) == null
+ && !has(UseLocalDisk.class, testDesc.getTestClass()))
+ && !forceLocalDisk(),
+ testDesc.getAnnotation(NoHttpd.class) == null
+ && !has(NoHttpd.class, testDesc.getTestClass()),
+ testDesc.getAnnotation(Sandboxed.class) != null
+ || has(Sandboxed.class, testDesc.getTestClass()),
+ testDesc.getAnnotation(UseSsh.class) != null
+ || has(UseSsh.class, testDesc.getTestClass()),
+ testDesc.getAnnotation(GerritConfig.class),
+ testDesc.getAnnotation(GerritConfigs.class),
+ testDesc.getAnnotation(GlobalPluginConfig.class),
+ testDesc.getAnnotation(GlobalPluginConfigs.class),
+ getLogLevelThresholdAnnotation(testDesc));
+ }
+
+ private static boolean has(Class<? extends Annotation> annotation, Class<?> clazz) {
+ for (; clazz != null; clazz = clazz.getSuperclass()) {
+ if (clazz.getAnnotation(annotation) != null) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private static Level getLogLevelThresholdAnnotation(org.junit.runner.Description testDesc) {
+ LogThreshold logLevelThreshold = testDesc.getTestClass().getAnnotation(LogThreshold.class);
+ if (logLevelThreshold == null) {
+ return Level.DEBUG;
+ }
+ return Level.toLevel(logLevelThreshold.level());
+ }
+
+ abstract org.junit.runner.Description testDescription();
+
+ @Nullable
+ abstract String configName();
+
+ abstract boolean memory();
+
+ abstract boolean httpd();
+
+ abstract boolean sandboxed();
+
+ abstract boolean useSshAnnotation();
+
+ boolean useSsh() {
+ return useSshAnnotation() && SshMode.useSsh();
+ }
+
+ @Nullable
+ abstract GerritConfig config();
+
+ @Nullable
+ abstract GerritConfigs configs();
+
+ @Nullable
+ abstract GlobalPluginConfig pluginConfig();
+
+ @Nullable
+ abstract GlobalPluginConfigs pluginConfigs();
+
+ abstract Level logLevelThreshold();
+
+ private void checkValidAnnotations() {
+ if (configs() != null && config() != null) {
+ throw new IllegalStateException("Use either @GerritConfigs or @GerritConfig not both");
+ }
+ if (pluginConfigs() != null && pluginConfig() != null) {
+ throw new IllegalStateException(
+ "Use either @GlobalPluginConfig or @GlobalPluginConfigs not both");
+ }
+ if ((pluginConfigs() != null || pluginConfig() != null) && memory()) {
+ throw new IllegalStateException("Must use @UseLocalDisk with @GlobalPluginConfig(s)");
+ }
+ }
+
+ private Config buildConfig(Config baseConfig) {
+ if (configs() != null) {
+ return ConfigAnnotationParser.parse(baseConfig, configs());
+ } else if (config() != null) {
+ return ConfigAnnotationParser.parse(baseConfig, config());
+ } else {
+ return baseConfig;
+ }
+ }
+
+ private Map<String, Config> buildPluginConfigs() {
+ if (pluginConfigs() != null) {
+ return ConfigAnnotationParser.parse(pluginConfigs());
+ } else if (pluginConfig() != null) {
+ return ConfigAnnotationParser.parse(pluginConfig());
+ }
+ return new HashMap<>();
+ }
+ }
+
+ private static final ImmutableMap<String, Level> LOG_LEVELS =
+ ImmutableMap.<String, Level>builder()
+ .put("com.google.gerrit", Level.DEBUG)
+
+ // Silence non-critical messages from MINA SSHD.
+ .put("org.apache.mina", Level.WARN)
+ .put("org.apache.sshd.common", Level.WARN)
+ .put("org.apache.sshd.server", Level.WARN)
+ .put("org.apache.sshd.common.keyprovider.FileKeyPairProvider", Level.INFO)
+ .put("com.google.gerrit.sshd.GerritServerSession", Level.WARN)
+
+ // Silence non-critical messages from mime-util.
+ .put("eu.medsea.mimeutil", Level.WARN)
+
+ // Silence non-critical messages from openid4java.
+ .put("org.apache.xml", Level.WARN)
+ .put("org.openid4java", Level.WARN)
+ .put("org.openid4java.consumer.ConsumerManager", Level.FATAL)
+ .put("org.openid4java.discovery.Discovery", Level.ERROR)
+ .put("org.openid4java.server.RealmVerifier", Level.ERROR)
+ .put("org.openid4java.message.AuthSuccess", Level.ERROR)
+
+ // Silence non-critical messages from c3p0 (if used).
+ .put("com.mchange.v2.c3p0", Level.WARN)
+ .put("com.mchange.v2.resourcepool", Level.WARN)
+ .put("com.mchange.v2.sql", Level.WARN)
+
+ // Silence non-critical messages from apache.http.
+ .put("org.apache.http", Level.WARN)
+
+ // Silence non-critical messages from Jetty.
+ .put("org.eclipse.jetty", Level.WARN)
+
+ // Silence non-critical messages from JGit.
+ .put("org.eclipse.jgit.transport.PacketLineIn", Level.WARN)
+ .put("org.eclipse.jgit.transport.PacketLineOut", Level.WARN)
+ .build();
+
+ private static boolean forceLocalDisk() {
+ String value = Strings.nullToEmpty(System.getenv("GERRIT_FORCE_LOCAL_DISK"));
+ if (value.isEmpty()) {
+ value = Strings.nullToEmpty(System.getProperty("gerrit.forceLocalDisk"));
+ }
+ switch (value.trim().toLowerCase(Locale.US)) {
+ case "1":
+ case "yes":
+ case "true":
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ /**
+ * Initializes on-disk site but does not start server.
+ *
+ * @param desc server description
+ * @param baseConfig default config values; merged with config from {@code desc} and then written
+ * into {@code site/etc/gerrit.config}.
+ * @param site temp directory where site will live.
+ * @throws Exception
+ */
+ public static void init(Description desc, Config baseConfig, Path site) throws Exception {
+ checkArgument(!desc.memory(), "can't initialize site path for in-memory test: %s", desc);
+ Config cfg = desc.buildConfig(baseConfig);
+ Map<String, Config> pluginConfigs = desc.buildPluginConfigs();
+
+ MergeableFileBasedConfig gerritConfig =
+ new MergeableFileBasedConfig(
+ site.resolve("etc").resolve("gerrit.config").toFile(), FS.DETECTED);
+ gerritConfig.load();
+ gerritConfig.merge(cfg);
+ mergeTestConfig(gerritConfig);
+ gerritConfig.save();
+
+ Init init = new Init();
+ int rc =
+ init.main(
+ new String[] {
+ "-d", site.toString(), "--batch", "--no-auto-start", "--skip-plugins",
+ });
+ if (rc != 0) {
+ throw new RuntimeException("Couldn't initialize site");
+ }
+
+ for (String pluginName : pluginConfigs.keySet()) {
+ MergeableFileBasedConfig pluginCfg =
+ new MergeableFileBasedConfig(
+ site.resolve("etc").resolve(pluginName + ".config").toFile(), FS.DETECTED);
+ pluginCfg.load();
+ pluginCfg.merge(pluginConfigs.get(pluginName));
+ pluginCfg.save();
+ }
+ }
+
+ /**
+ * Initializes new Gerrit site and returns started server.
+ *
+ * <p>A new temporary directory for the site will be created with {@link TempFileUtil}, even in
+ * the server is otherwise configured in-memory. Closing the server stops the daemon but does not
+ * delete the temporary directory. Callers may either get the directory with {@link
+ * #getSitePath()} and delete it manually, or call {@link TempFileUtil#cleanup()}.
+ *
+ * @param desc server description.
+ * @param baseConfig default config values; merged with config from {@code desc}.
+ * @param testSysModule additional Guice module to use.
+ * @return started server.
+ * @throws Exception
+ */
+ public static GerritServer initAndStart(
+ Description desc, Config baseConfig, @Nullable Module testSysModule) throws Exception {
+ Path site = TempFileUtil.createTempDirectory().toPath();
+ try {
+ if (!desc.memory()) {
+ init(desc, baseConfig, site);
+ }
+ return start(desc, baseConfig, site, testSysModule, null, null);
+ } catch (Exception e) {
+ TempFileUtil.recursivelyDelete(site.toFile());
+ throw e;
+ }
+ }
+
+ /**
+ * Starts Gerrit server from existing on-disk site.
+ *
+ * @param desc server description.
+ * @param baseConfig default config values; merged with config from {@code desc}.
+ * @param site existing temporary directory for site. Required, but may be empty, for in-memory
+ * servers. For on-disk servers, assumes that {@link #init} was previously called to
+ * initialize this directory. Can be retrieved from the returned instance via {@link
+ * #getSitePath()}.
+ * @param testSysModule optional additional module to add to the system injector.
+ * @param inMemoryRepoManager {@link InMemoryRepositoryManager} that should be used if the site is
+ * started in memory
+ * @param inMemoryDatabaseInstance {@link com.google.gerrit.testing.InMemoryDatabase.Instance}
+ * that should be used if the site is started in memory
+ * @param additionalArgs additional command-line arguments for the daemon program; only allowed if
+ * the test is not in-memory.
+ * @return started server.
+ * @throws Exception
+ */
+ public static GerritServer start(
+ Description desc,
+ Config baseConfig,
+ Path site,
+ @Nullable Module testSysModule,
+ @Nullable InMemoryRepositoryManager inMemoryRepoManager,
+ @Nullable InMemoryDatabase.Instance inMemoryDatabaseInstance,
+ String... additionalArgs)
+ throws Exception {
+ checkArgument(site != null, "site is required (even for in-memory server");
+ desc.checkValidAnnotations();
+ configureLogging(desc.logLevelThreshold());
+ CyclicBarrier serverStarted = new CyclicBarrier(2);
+ Daemon daemon =
+ new Daemon(
+ () -> {
+ try {
+ serverStarted.await();
+ } catch (InterruptedException | BrokenBarrierException e) {
+ throw new RuntimeException(e);
+ }
+ },
+ site);
+ daemon.setEmailModuleForTesting(new FakeEmailSender.Module());
+ daemon.setAuditEventModuleForTesting(new FakeGroupAuditService.Module());
+ if (testSysModule != null) {
+ daemon.addAdditionalSysModuleForTesting(testSysModule);
+ }
+ daemon.setEnableSshd(desc.useSsh());
+
+ if (desc.memory()) {
+ checkArgument(additionalArgs.length == 0, "cannot pass args to in-memory server");
+ return startInMemory(
+ desc, site, baseConfig, daemon, inMemoryRepoManager, inMemoryDatabaseInstance);
+ }
+ return startOnDisk(desc, site, daemon, serverStarted, additionalArgs);
+ }
+
+ private static GerritServer startInMemory(
+ Description desc,
+ Path site,
+ Config baseConfig,
+ Daemon daemon,
+ @Nullable InMemoryRepositoryManager inMemoryRepoManager,
+ @Nullable InMemoryDatabase.Instance inMemoryDatabaseInstance)
+ throws Exception {
+ Config cfg = desc.buildConfig(baseConfig);
+ daemon.setSlave(isSlave(baseConfig) || cfg.getBoolean("container", "slave", false));
+ mergeTestConfig(cfg);
+ // Set the log4j configuration to an invalid one to prevent system logs
+ // from getting configured and creating log files.
+ System.setProperty(SystemLog.LOG4J_CONFIGURATION, "invalidConfiguration");
+ cfg.setBoolean("httpd", null, "requestLog", false);
+ cfg.setBoolean("sshd", null, "requestLog", false);
+ cfg.setBoolean("index", "lucene", "testInmemory", true);
+ cfg.setBoolean("index", null, "onlineUpgrade", false);
+ cfg.setString("gitweb", null, "cgi", "");
+ daemon.setEnableHttpd(desc.httpd());
+ daemon.setLuceneModule(LuceneIndexModule.singleVersionAllLatest(0, isSlave(baseConfig)));
+ daemon.setDatabaseForTesting(
+ ImmutableList.<Module>of(
+ new InMemoryTestingDatabaseModule(
+ cfg, site, inMemoryRepoManager, inMemoryDatabaseInstance),
+ new AbstractModule() {
+ @Override
+ protected void configure() {
+ bind(GerritRuntime.class).toInstance(GerritRuntime.DAEMON);
+ }
+ }));
+ daemon.addAdditionalSysModuleForTesting(
+ new ReindexProjectsAtStartup.Module(), new ReindexGroupsAtStartup.Module());
+ daemon.start();
+ return new GerritServer(desc, null, createTestInjector(daemon), daemon, null);
+ }
+
+ private static boolean isSlave(Config baseConfig) {
+ return baseConfig.getBoolean("container", "slave", false);
+ }
+
+ private static GerritServer startOnDisk(
+ Description desc,
+ Path site,
+ Daemon daemon,
+ CyclicBarrier serverStarted,
+ String[] additionalArgs)
+ throws Exception {
+ requireNonNull(site);
+ ExecutorService daemonService = Executors.newSingleThreadExecutor();
+ String[] args =
+ Stream.concat(
+ Stream.of(
+ "-d", site.toString(), "--headless", "--console-log", "--show-stack-trace"),
+ Arrays.stream(additionalArgs))
+ .toArray(String[]::new);
+ @SuppressWarnings("unused")
+ Future<?> possiblyIgnoredError =
+ daemonService.submit(
+ () -> {
+ int rc = daemon.main(args);
+ if (rc != 0) {
+ System.err.println("Failed to start Gerrit daemon");
+ serverStarted.reset();
+ }
+ return null;
+ });
+ try {
+ serverStarted.await();
+ } catch (BrokenBarrierException e) {
+ daemon.stop();
+ throw new StartupException("Failed to start Gerrit daemon; see log", e);
+ }
+ System.out.println("Gerrit Server Started");
+
+ return new GerritServer(desc, site, createTestInjector(daemon), daemon, daemonService);
+ }
+
+ private static void configureLogging(Level threshold) {
+ LogManager.resetConfiguration();
+
+ PatternLayout layout = new PatternLayout();
+ layout.setConversionPattern("%-5p %c %x: %m%n");
+
+ ConsoleAppender dst = new ConsoleAppender();
+ dst.setLayout(layout);
+ dst.setTarget("System.err");
+ dst.setThreshold(threshold);
+ dst.activateOptions();
+
+ Logger root = LogManager.getRootLogger();
+ root.removeAllAppenders();
+ root.addAppender(dst);
+
+ LOG_LEVELS.entrySet().stream().forEach(e -> getLogger(e.getKey()).setLevel(e.getValue()));
+ }
+
+ private static void mergeTestConfig(Config cfg) {
+ String forceEphemeralPort = String.format("%s:0", getLocalHost().getHostName());
+ String url = "http://" + forceEphemeralPort + "/";
+ cfg.setString("gerrit", null, "canonicalWebUrl", url);
+ cfg.setString("httpd", null, "listenUrl", url);
+
+ if (cfg.getString("sshd", null, "listenAddress") == null) {
+ cfg.setString("sshd", null, "listenAddress", forceEphemeralPort);
+ }
+ cfg.setBoolean("sshd", null, "testUseInsecureRandom", true);
+ cfg.unset("cache", null, "directory");
+ cfg.setString("gerrit", null, "basePath", "git");
+ cfg.setBoolean("sendemail", null, "enable", true);
+ cfg.setInt("sendemail", null, "threadPoolSize", 0);
+ cfg.setInt("cache", "projects", "checkFrequency", 0);
+ cfg.setInt("plugins", null, "checkFrequency", 0);
+
+ cfg.setInt("sshd", null, "threads", 1);
+ cfg.setInt("sshd", null, "commandStartThreads", 1);
+ cfg.setInt("receive", null, "threadPoolSize", 1);
+ cfg.setInt("index", null, "threads", 1);
+ cfg.setBoolean("index", null, "reindexAfterRefUpdate", false);
+
+ NoteDbMode.newNotesMigrationFromEnv().setConfigValues(cfg);
+ }
+
+ private static Injector createTestInjector(Daemon daemon) throws Exception {
+ Injector sysInjector = get(daemon, "sysInjector");
+ Module module =
+ new FactoryModule() {
+ @Override
+ protected void configure() {
+ bindConstant().annotatedWith(SshEnabled.class).to(daemon.getEnableSshd());
+ bind(AccountCreator.class);
+ bind(AccountOperations.class).to(AccountOperationsImpl.class);
+ bind(GroupOperations.class).to(GroupOperationsImpl.class);
+ factory(PushOneCommit.Factory.class);
+ install(InProcessProtocol.module());
+ install(new NoSshModule());
+ install(new AsyncReceiveCommits.Module());
+ factory(ProjectResetter.Builder.Factory.class);
+ }
+ };
+ return sysInjector.createChildInjector(module);
+ }
+
+ @SuppressWarnings("unchecked")
+ private static <T> T get(Object obj, String field)
+ throws SecurityException, NoSuchFieldException, IllegalArgumentException,
+ IllegalAccessException {
+ Field f = obj.getClass().getDeclaredField(field);
+ f.setAccessible(true);
+ return (T) f.get(obj);
+ }
+
+ private static InetAddress getLocalHost() {
+ return InetAddress.getLoopbackAddress();
+ }
+
+ private final Description desc;
+ private final Path sitePath;
+
+ private Daemon daemon;
+ private ExecutorService daemonService;
+ private Injector testInjector;
+ private String url;
+ private InetSocketAddress sshdAddress;
+ private InetSocketAddress httpAddress;
+
+ private GerritServer(
+ Description desc,
+ @Nullable Path sitePath,
+ Injector testInjector,
+ Daemon daemon,
+ @Nullable ExecutorService daemonService) {
+ this.desc = requireNonNull(desc);
+ this.sitePath = sitePath;
+ this.testInjector = requireNonNull(testInjector);
+ this.daemon = requireNonNull(daemon);
+ this.daemonService = daemonService;
+
+ Config cfg = testInjector.getInstance(Key.get(Config.class, GerritServerConfig.class));
+ url = cfg.getString("gerrit", null, "canonicalWebUrl");
+ URI uri = URI.create(url);
+
+ String addr = cfg.getString("sshd", null, "listenAddress");
+ // We do not use InitSshd.isOff to avoid coupling GerritServer to the SSH code.
+ if (!"off".equalsIgnoreCase(addr)) {
+ sshdAddress = SocketUtil.resolve(cfg.getString("sshd", null, "listenAddress"), 0);
+ }
+ httpAddress = new InetSocketAddress(uri.getHost(), uri.getPort());
+ }
+
+ String getUrl() {
+ return url;
+ }
+
+ InetSocketAddress getSshdAddress() {
+ return sshdAddress;
+ }
+
+ InetSocketAddress getHttpAddress() {
+ return httpAddress;
+ }
+
+ public Injector getTestInjector() {
+ return testInjector;
+ }
+
+ Description getDescription() {
+ return desc;
+ }
+
+ public static GerritServer restartAsSlave(GerritServer server) throws Exception {
+ checkState(server.desc.sandboxed(), "restarting as slave requires @Sandboxed");
+
+ Path site = server.testInjector.getInstance(Key.get(Path.class, SitePath.class));
+
+ Config cfg = server.testInjector.getInstance(Key.get(Config.class, GerritServerConfig.class));
+ cfg.setBoolean("container", null, "slave", true);
+
+ InMemoryRepositoryManager inMemoryRepoManager = null;
+ if (hasBinding(server.testInjector, InMemoryRepositoryManager.class)) {
+ inMemoryRepoManager = server.testInjector.getInstance(InMemoryRepositoryManager.class);
+ }
+
+ InMemoryDatabase.Instance dbInstance = null;
+ if (hasBinding(server.testInjector, InMemoryDatabase.class)) {
+ InMemoryDatabase inMemoryDatabase = server.testInjector.getInstance(InMemoryDatabase.class);
+ dbInstance = inMemoryDatabase.getDbInstance();
+ dbInstance.setKeepOpen(true);
+ }
+ try {
+ server.close();
+ server.daemon.stop();
+ return start(server.desc, cfg, site, null, inMemoryRepoManager, dbInstance);
+ } finally {
+ if (dbInstance != null) {
+ dbInstance.setKeepOpen(false);
+ }
+ }
+ }
+
+ private static boolean hasBinding(Injector injector, Class<?> clazz) {
+ return injector.getExistingBinding(Key.get(clazz)) != null;
+ }
+
+ @Override
+ public void close() throws Exception {
+ try {
+ checkNoteDbState();
+ } finally {
+ daemon.getLifecycleManager().stop();
+ if (daemonService != null) {
+ System.out.println("Gerrit Server Shutdown");
+ daemonService.shutdownNow();
+ daemonService.awaitTermination(Long.MAX_VALUE, TimeUnit.SECONDS);
+ }
+ RepositoryCache.clear();
+ }
+ }
+
+ public Path getSitePath() {
+ return sitePath;
+ }
+
+ private void checkNoteDbState() throws Exception {
+ NoteDbMode mode = NoteDbMode.get();
+ if (mode != NoteDbMode.CHECK && mode != NoteDbMode.PRIMARY) {
+ return;
+ }
+ NoteDbChecker checker = testInjector.getInstance(NoteDbChecker.class);
+ OneOffRequestContext oneOffRequestContext =
+ testInjector.getInstance(OneOffRequestContext.class);
+ try (ManualRequestContext ctx = oneOffRequestContext.open()) {
+ if (mode == NoteDbMode.CHECK) {
+ checker.rebuildAndCheckAllChanges();
+ } else if (mode == NoteDbMode.PRIMARY) {
+ checker.assertNoReviewDbChanges(desc.testDescription());
+ }
+ }
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(this).addValue(desc).toString();
+ }
+}