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>() {}.getType()); + Set names = result.keySet(); + assertTrue(names.contains("Administrators")); + assertTrue(names.contains("Administrators")); + assertTrue(names.contains("Anonymous Users")); + assertTrue(names.contains("Non-Interactive Users")); + assertTrue(names.contains("Project Owners")); + assertTrue(names.contains("Registered Users")); + } + + @Test + public void systemGroupsCreated_internals() throws OrmException { + ReviewDb db = reviewDbProvider.open(); + try { + Set names = Sets.newHashSet(); + for (AccountGroup g : db.accountGroups().all()) { + names.add(g.getName()); + } + assertTrue(names.contains("Administrators")); + assertTrue(names.contains("Administrators")); + assertTrue(names.contains("Anonymous Users")); + assertTrue(names.contains("Non-Interactive Users")); + assertTrue(names.contains("Project Owners")); + assertTrue(names.contains("Registered Users")); + } finally { + db.close(); + } + } +} diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/TestAccount.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/TestAccount.java new file mode 100644 index 0000000000..b79408554c --- /dev/null +++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/TestAccount.java @@ -0,0 +1,43 @@ +// 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 com.jcraft.jsch.KeyPair; + + +public class TestAccount { + final String username; + final String email; + final String fullName; + final KeyPair sshKey; + final String httpPassword; + + TestAccount(String username, String email, String fullName, + KeyPair sshKey, String httpPassword) { + this.username = username; + this.email = email; + this.fullName = fullName; + this.sshKey = sshKey; + this.httpPassword = httpPassword; + } + + byte[] privateKey() { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + sshKey.writePrivateKey(out); + return out.toByteArray(); + } +} diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Daemon.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Daemon.java index 4ab858d22f..6e825ef0fc 100644 --- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Daemon.java +++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Daemon.java @@ -128,6 +128,15 @@ public class Daemon extends SiteProgram { private Injector httpdInjector; private File runFile; + private Runnable serverStarted; + + public Daemon() { + } + + public Daemon(Runnable serverStarted) { + this.serverStarted = serverStarted; + } + @Override public int run() throws Exception { mustHaveValidSite(); @@ -204,6 +213,10 @@ public class Daemon extends SiteProgram { } } + if (serverStarted != null) { + serverStarted.run(); + } + RuntimeShutdown.waitFor(); return 0; } catch (Throwable err) { diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/RuntimeShutdown.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/RuntimeShutdown.java index 18b706442d..c00ad7fefc 100644 --- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/RuntimeShutdown.java +++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/RuntimeShutdown.java @@ -103,9 +103,7 @@ public class RuntimeShutdown { try { wait(); } catch (InterruptedException e) { - log.warn("Thread " + Thread.currentThread().getName() - + " interrupted while waiting for graceful shutdown;" - + " ignoring interrupt request"); + return; } } } diff --git a/pom.xml b/pom.xml index ac25d79e46..57ec0b6b6d 100644 --- a/pom.xml +++ b/pom.xml @@ -91,6 +91,8 @@ limitations under the License. gerrit-extension-api gerrit-gwtui + + gerrit-acceptance-tests @@ -571,6 +573,12 @@ limitations under the License. 0.5.1-r1095809 + + org.apache.httpcomponents + httpclient + 4.0 + + com.jcraft jsch