diff --git a/gerrit-acceptance-tests/.gitignore b/gerrit-acceptance-tests/.gitignore
new file mode 100644
index 0000000000..cbfe724945
--- /dev/null
+++ b/gerrit-acceptance-tests/.gitignore
@@ -0,0 +1,7 @@
+/target
+/.classpath
+/.project
+/.settings/org.eclipse.core.resources.prefs
+/.settings/org.eclipse.jdt.core.prefs
+/.settings/org.eclipse.m2e.core.prefs
+/gerrit-common.iml
diff --git a/gerrit-acceptance-tests/pom.xml b/gerrit-acceptance-tests/pom.xml
new file mode 100644
index 0000000000..5693b18d08
--- /dev/null
+++ b/gerrit-acceptance-tests/pom.xml
@@ -0,0 +1,155 @@
+
+
+
+ 4.0.0
+
+
+ com.google.gerrit
+ gerrit-parent
+ 2.6-SNAPSHOT
+
+
+ gerrit-acceptance-tests
+
+ Gerrit Code Review - Acceptance Tests
+
+
+ Gerrit Acceptance Tests
+
+
+
+
+ junit
+ junit
+ test
+
+
+
+ com.google.gerrit
+ gerrit-reviewdb
+ ${project.version}
+
+
+
+ com.google.gerrit
+ gerrit-main
+ ${project.version}
+ runtime
+
+
+
+ com.google.gerrit
+ gerrit-server
+ ${project.version}
+
+
+ org.apache.tomcat
+ servlet-api
+
+
+
+
+
+ bouncycastle
+ bcprov-jdk15
+ provided
+
+
+
+ bouncycastle
+ bcpg-jdk15
+ provided
+
+
+
+ org.slf4j
+ slf4j-log4j12
+
+
+
+ log4j
+ log4j
+
+
+
+ com.google.gerrit
+ gerrit-openid
+ ${project.version}
+
+
+
+ com.google.gerrit
+ gerrit-sshd
+ ${project.version}
+
+
+
+ com.google.gerrit
+ gerrit-httpd
+ ${project.version}
+
+
+
+ com.google.gerrit
+ gerrit-pgm
+ ${project.version}
+
+
+ org.eclipse.jetty
+ jetty-servlet
+
+
+
+
+
+ org.eclipse.jetty
+ jetty-servlet
+ provided
+
+
+
+
+
+ acceptance
+
+
+
+ org.apache.maven.plugins
+ maven-failsafe-plugin
+ 2.5
+
+
+ integration-test
+
+ integration-test
+
+
+
+ verify
+
+ verify
+
+
+
+
+
+
+
+
+
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
new file mode 100644
index 0000000000..51c7b3d269
--- /dev/null
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/AbstractDaemonTest.java
@@ -0,0 +1,35 @@
+// 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 org.junit.After;
+import org.junit.Before;
+
+
+public abstract class AbstractDaemonTest {
+
+ private GerritServer server;
+
+ @Before
+ public final void beforeTest() throws Exception {
+ server = GerritServer.start();
+ server.getTestInjector().injectMembers(this);
+ }
+
+ @After
+ public final void afterTest() throws Exception {
+ server.stop();
+ }
+}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/AccountCreator.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/AccountCreator.java
new file mode 100644
index 0000000000..df795a5d1f
--- /dev/null
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/AccountCreator.java
@@ -0,0 +1,124 @@
+// 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 java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.UnsupportedEncodingException;
+import java.util.Collections;
+
+import javax.inject.Inject;
+
+import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.reviewdb.client.AccountExternalId;
+import com.google.gerrit.reviewdb.client.AccountGroup;
+import com.google.gerrit.reviewdb.client.AccountGroupMember;
+import com.google.gerrit.reviewdb.client.AccountSshKey;
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.account.AccountByEmailCache;
+import com.google.gerrit.server.account.AccountCache;
+import com.google.gerrit.server.account.GroupCache;
+import com.google.gerrit.server.ssh.SshKeyCache;
+import com.google.gwtorm.server.OrmException;
+import com.google.gwtorm.server.SchemaFactory;
+import com.jcraft.jsch.ChannelExec;
+import com.jcraft.jsch.JSch;
+import com.jcraft.jsch.JSchException;
+import com.jcraft.jsch.KeyPair;
+import com.jcraft.jsch.Session;
+
+class AccountCreator {
+
+ private SchemaFactory reviewDbProvider;
+ private GroupCache groupCache;
+ private SshKeyCache sshKeyCache;
+ private AccountCache accountCache;
+ private AccountByEmailCache byEmailCache;
+
+ @Inject
+ AccountCreator(SchemaFactory schema, GroupCache groupCache,
+ SshKeyCache sshKeyCache, AccountCache accountCache,
+ AccountByEmailCache byEmailCache) {
+ reviewDbProvider = schema;
+ this.groupCache = groupCache;
+ this.sshKeyCache = sshKeyCache;
+ this.accountCache = accountCache;
+ this.byEmailCache = byEmailCache;
+ }
+
+ TestAccount create(String username, String email, String fullName,
+ String... groups)
+ throws OrmException, UnsupportedEncodingException, JSchException {
+ ReviewDb db = reviewDbProvider.open();
+ try {
+ Account.Id id = new Account.Id(db.nextAccountId());
+ KeyPair sshKey = genSshKey();
+ AccountSshKey key =
+ new AccountSshKey(new AccountSshKey.Id(id, 1), publicKey(sshKey, email));
+ AccountExternalId extUser =
+ new AccountExternalId(id, new AccountExternalId.Key(
+ AccountExternalId.SCHEME_USERNAME, username));
+ String httpPass = "http-pass";
+ extUser.setPassword(httpPass);
+ db.accountExternalIds().insert(Collections.singleton(extUser));
+
+ if (email != null) {
+ AccountExternalId extMailto = new AccountExternalId(id, getEmailKey(email));
+ extMailto.setEmailAddress(email);
+ db.accountExternalIds().insert(Collections.singleton(extMailto));
+ }
+
+ Account a = new Account(id);
+ a.setFullName(fullName);
+ a.setPreferredEmail(email);
+ db.accounts().insert(Collections.singleton(a));
+
+ db.accountSshKeys().insert(Collections.singleton(key));
+
+ for (String n : groups) {
+ AccountGroup.NameKey k = new AccountGroup.NameKey(n);
+ AccountGroup g = groupCache.get(k);
+ AccountGroupMember m =
+ new AccountGroupMember(new AccountGroupMember.Key(id, g.getId()));
+ db.accountGroupMembers().insert(Collections.singleton(m));
+ }
+
+ sshKeyCache.evict(username);
+ accountCache.evictByUsername(username);
+ byEmailCache.evict(email);
+
+ return new TestAccount(username, email, fullName, sshKey, httpPass);
+ } finally {
+ db.close();
+ }
+ }
+
+ private AccountExternalId.Key getEmailKey(String email) {
+ return new AccountExternalId.Key(AccountExternalId.SCHEME_MAILTO, email);
+ }
+
+ private static KeyPair genSshKey() throws JSchException {
+ JSch jsch = new JSch();
+ return KeyPair.genKeyPair(jsch, KeyPair.RSA);
+ }
+
+ private static String publicKey(KeyPair sshKey, String comment)
+ throws UnsupportedEncodingException {
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ sshKey.writePublicKey(out, comment);
+ return out.toString("ASCII");
+ }
+}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/GerritServer.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/GerritServer.java
new file mode 100644
index 0000000000..06fa95c6f5
--- /dev/null
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/GerritServer.java
@@ -0,0 +1,126 @@
+// 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 java.lang.reflect.Field;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.concurrent.BrokenBarrierException;
+import java.util.concurrent.Callable;
+import java.util.concurrent.CyclicBarrier;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+
+import com.google.gerrit.lifecycle.LifecycleManager;
+import com.google.gerrit.pgm.Daemon;
+import com.google.gerrit.pgm.Init;
+import com.google.gerrit.server.config.FactoryModule;
+import com.google.inject.Injector;
+import com.google.inject.Module;
+
+class GerritServer {
+
+ /** Returns fully started Gerrit server */
+ static GerritServer start() throws Exception {
+
+ final String sitePath = initSite();
+
+ final CyclicBarrier serverStarted = new CyclicBarrier(2);
+
+ final Daemon daemon = new Daemon(new Runnable() {
+ public void run() {
+ try {
+ serverStarted.await();
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ } catch (BrokenBarrierException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ });
+
+ ExecutorService daemonService = Executors.newSingleThreadExecutor();
+ daemonService.submit(new Callable() {
+ public Void call() throws Exception {
+ int rc = daemon.main(new String[] {"-d", sitePath, "--headless" });
+ if (rc != 0) {
+ serverStarted.reset();
+ }
+ return null;
+ };
+ });
+
+ serverStarted.await();
+ System.out.println("Gerrit Server Started");
+
+ Injector i = createTestInjector(daemon);
+ return new GerritServer(i, daemon, daemonService);
+ }
+
+ private static String initSite() throws Exception {
+ DateFormat df = new SimpleDateFormat("yyyyMMddHHmmss");
+ String path = "target/test_site_" + df.format(new Date());
+ Init init = new Init();
+ int rc = init.main(new String[] {"-d", path, "--batch", "--no-auto-start"});
+ if (rc != 0) {
+ throw new RuntimeException("Couldn't initialize site");
+ }
+ return path;
+ }
+
+ private static Injector createTestInjector(Daemon daemon) throws Exception {
+ Injector sysInjector = get(daemon, "sysInjector");
+ Module module = new FactoryModule() {
+ @Override
+ protected void configure() {
+ bind(AccountCreator.class);
+ }
+ };
+ return sysInjector.createChildInjector(module);
+ }
+
+ @SuppressWarnings("unchecked")
+ private static 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 Daemon daemon;
+ private ExecutorService daemonService;
+ private Injector testInjector;
+
+ private GerritServer(Injector testInjector,
+ Daemon daemon, ExecutorService daemonService) {
+ this.testInjector = testInjector;
+ this.daemon = daemon;
+ this.daemonService = daemonService;
+ }
+
+ Injector getTestInjector() {
+ return testInjector;
+ }
+
+ void stop() throws Exception {
+ LifecycleManager manager = get(daemon, "manager");
+ System.out.println("Gerrit Server Shutdown");
+ manager.stop();
+ daemonService.shutdownNow();
+ daemonService.awaitTermination(Long.MAX_VALUE, TimeUnit.SECONDS);
+ }
+}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/RestSession.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/RestSession.java
new file mode 100644
index 0000000000..beb1785968
--- /dev/null
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/RestSession.java
@@ -0,0 +1,58 @@
+// 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 java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.Reader;
+
+import org.apache.http.HttpResponse;
+import org.apache.http.auth.AuthScope;
+import org.apache.http.auth.UsernamePasswordCredentials;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.impl.client.DefaultHttpClient;
+
+class RestSession {
+
+ private final TestAccount account;
+ DefaultHttpClient client;
+
+ RestSession(TestAccount account) {
+ this.account = account;
+ }
+
+ Reader get(String endPoint) throws IOException {
+ HttpGet get = new HttpGet("http://localhost:8080/a" + endPoint);
+ HttpResponse response = getClient().execute(get);
+ Reader reader = new InputStreamReader(response.getEntity().getContent());
+ reader.skip(4);
+ return reader;
+ }
+
+ Reader post(String endPoint) {
+ // TODO
+ return null;
+ }
+
+ private DefaultHttpClient getClient() {
+ if (client == null) {
+ client = new DefaultHttpClient();
+ client.getCredentialsProvider().setCredentials(
+ new AuthScope("localhost", 8080),
+ new UsernamePasswordCredentials(account.username, account.httpPassword));
+ }
+ return client;
+ }
+}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/SshSession.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/SshSession.java
new file mode 100644
index 0000000000..04c88d7f4c
--- /dev/null
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/SshSession.java
@@ -0,0 +1,68 @@
+// 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 java.io.IOException;
+import java.io.InputStream;
+import java.util.Scanner;
+
+import com.jcraft.jsch.ChannelExec;
+import com.jcraft.jsch.JSch;
+import com.jcraft.jsch.JSchException;
+import com.jcraft.jsch.Session;
+
+class SshSession {
+
+ private final TestAccount account;
+ private Session session;
+
+ SshSession(TestAccount account) {
+ this.account = account;
+ }
+
+ String exec(String command) throws JSchException, IOException {
+ ChannelExec channel = (ChannelExec) getSession().openChannel("exec");
+ try {
+ channel.setCommand(command);
+ channel.setInputStream(null);
+ InputStream in = channel.getInputStream();
+ channel.connect();
+
+ Scanner s = new Scanner(in).useDelimiter("\\A");
+ return s.hasNext() ? s.next() : "";
+ } finally {
+ channel.disconnect();
+ }
+ }
+
+ void close() {
+ if (session != null) {
+ session.disconnect();
+ session = null;
+ }
+ }
+
+ private Session getSession() throws JSchException {
+ if (session == null) {
+ JSch jsch = new JSch();
+ jsch.addIdentity("KeyPair",
+ account.privateKey(), account.sshKey.getPublicKeyBlob(), null);
+ session = jsch.getSession(account.username, "localhost", 29418);
+ session.setConfig("StrictHostKeyChecking", "no");
+ session.connect();
+ }
+ return session;
+ }
+}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/SystemGroupsIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/SystemGroupsIT.java
new file mode 100644
index 0000000000..ed43e5f0dd
--- /dev/null
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/SystemGroupsIT.java
@@ -0,0 +1,115 @@
+// 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 org.junit.Assert.assertTrue;
+
+import java.io.IOException;
+import java.io.Reader;
+import java.util.Map;
+import java.util.Set;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import com.google.common.collect.Sets;
+import com.google.common.reflect.TypeToken;
+import com.google.gerrit.reviewdb.client.AccountGroup;
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gson.Gson;
+import com.google.gwtorm.server.OrmException;
+import com.google.gwtorm.server.SchemaFactory;
+import com.google.inject.Inject;
+import com.jcraft.jsch.JSchException;
+
+/**
+ * An example test that tests presence of system groups in a newly initialized
+ * review site.
+ *
+ * The test shows how to perform these checks via SSH, REST or using Gerrit
+ * internals.
+ */
+public class SystemGroupsIT extends AbstractDaemonTest {
+
+ @Inject
+ private SchemaFactory reviewDbProvider;
+
+ @Inject
+ private AccountCreator accounts;
+
+ protected TestAccount admin;
+
+ @Before
+ public void setUp() throws Exception {
+ admin = accounts.create("admin", "admin@sap.com", "Administrator",
+ "Administrators");
+ }
+
+ @Test
+ public void systemGroupsCreated_ssh() throws JSchException, IOException {
+ SshSession session = new SshSession(admin);
+ String result = session.exec("gerrit ls-groups");
+ assertTrue(result.contains("Administrators"));
+ assertTrue(result.contains("Anonymous Users"));
+ assertTrue(result.contains("Non-Interactive Users"));
+ assertTrue(result.contains("Project Owners"));
+ assertTrue(result.contains("Registered Users"));
+ session.close();
+ }
+
+ private static class Group {
+ String id;
+ String name;
+ String url;
+ String description;
+ Integer groupId;
+ String ownerId;
+ };
+
+ @Test
+ public void systemGroupsCreated_rest() throws IOException {
+ RestSession session = new RestSession(admin);
+ Reader r = session.get("/groups/");
+ Gson gson = new Gson();
+ Map result =
+ gson.fromJson(r, new TypeToken