From f5eb0d93660786213b98dadde7d93c5605454495 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Tue, 12 Jan 2010 12:30:42 -0800 Subject: [PATCH] Add JUnit tests for HTTP transport No Eclipse support for this project is provided, because the Jetty project does not publish a complete P2 repository. Change-Id: Ic5fe2e79bb216e36920fd4a70ec15dd6ccfd1468 Signed-off-by: Shawn O. Pearce --- org.eclipse.jgit.http.test/.gitignore | 1 + org.eclipse.jgit.http.test/pom.xml | 108 ++++ .../jgit/http/test/AsIsServiceTest.java | 136 +++++ .../test/DefaultReceivePackFactoryTest.java | 198 +++++++ .../test/DefaultUploadPackFactoryTest.java | 160 +++++ .../http/test/DumbClientDumbServerTest.java | 246 ++++++++ .../http/test/DumbClientSmartServerTest.java | 261 ++++++++ .../jgit/http/test/ErrorServletTest.java | 85 +++ .../jgit/http/test/FileResolverTest.java | 137 +++++ .../jgit/http/test/GitServletInitTest.java | 119 ++++ .../jgit/http/test/HttpClientTests.java | 328 +++++++++++ .../http/test/SmartClientSmartServerTest.java | 556 ++++++++++++++++++ .../jgit/http/test/util/AccessEvent.java | 181 ++++++ .../jgit/http/test/util/AppServer.java | 295 ++++++++++ .../jgit/http/test/util/HttpTestCase.java | 161 +++++ .../http/test/util/MockServletConfig.java | 85 +++ .../jgit/http/test/util/RecordingLogger.java | 146 +++++ .../jgit/http/test/util/TestRequestLog.java | 71 +++ .../junit/LocalDiskRepositoryTestCase.java | 12 +- .../eclipse/jgit/junit/TestRepository.java | 162 ++++- .../eclipse/jgit/transport/TransportHttp.java | 36 +- pom.xml | 10 + 22 files changed, 3476 insertions(+), 18 deletions(-) create mode 100644 org.eclipse.jgit.http.test/.gitignore create mode 100644 org.eclipse.jgit.http.test/pom.xml create mode 100644 org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/AsIsServiceTest.java create mode 100644 org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/DefaultReceivePackFactoryTest.java create mode 100644 org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/DefaultUploadPackFactoryTest.java create mode 100644 org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/DumbClientDumbServerTest.java create mode 100644 org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/DumbClientSmartServerTest.java create mode 100644 org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/ErrorServletTest.java create mode 100644 org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/FileResolverTest.java create mode 100644 org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/GitServletInitTest.java create mode 100644 org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/HttpClientTests.java create mode 100644 org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/SmartClientSmartServerTest.java create mode 100644 org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/util/AccessEvent.java create mode 100644 org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/util/AppServer.java create mode 100644 org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/util/HttpTestCase.java create mode 100644 org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/util/MockServletConfig.java create mode 100644 org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/util/RecordingLogger.java create mode 100644 org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/util/TestRequestLog.java diff --git a/org.eclipse.jgit.http.test/.gitignore b/org.eclipse.jgit.http.test/.gitignore new file mode 100644 index 000000000..ea8c4bf7f --- /dev/null +++ b/org.eclipse.jgit.http.test/.gitignore @@ -0,0 +1 @@ +/target diff --git a/org.eclipse.jgit.http.test/pom.xml b/org.eclipse.jgit.http.test/pom.xml new file mode 100644 index 000000000..fe9363a3e --- /dev/null +++ b/org.eclipse.jgit.http.test/pom.xml @@ -0,0 +1,108 @@ + + + + + 4.0.0 + + + org.eclipse.jgit + org.eclipse.jgit-parent + 0.6.0-SNAPSHOT + + + org.eclipse.jgit.http.test + JGit - HTTP Tests + + + Tests for the HTTP transport. + + + + + junit + junit + test + + + + org.eclipse.jgit + org.eclipse.jgit + ${project.version} + test + + + + org.eclipse.jgit + org.eclipse.jgit.junit + ${project.version} + test + + + + org.eclipse.jgit + org.eclipse.jgit.http.server + ${project.version} + test + + + + org.eclipse.jetty + jetty-servlet + test + + + + + tst/ + + + + tst-rsrc/ + + + + diff --git a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/AsIsServiceTest.java b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/AsIsServiceTest.java new file mode 100644 index 000000000..ea937481d --- /dev/null +++ b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/AsIsServiceTest.java @@ -0,0 +1,136 @@ +/* + * Copyright (C) 2009-2010, Google Inc. + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.eclipse.jgit.http.test; + +import javax.servlet.http.HttpServletRequestWrapper; + +import org.eclipse.jetty.server.Request; +import org.eclipse.jgit.http.server.resolver.AsIsFileService; +import org.eclipse.jgit.http.server.resolver.ServiceNotAuthorizedException; +import org.eclipse.jgit.http.server.resolver.ServiceNotEnabledException; +import org.eclipse.jgit.junit.LocalDiskRepositoryTestCase; +import org.eclipse.jgit.lib.Repository; + +public class AsIsServiceTest extends LocalDiskRepositoryTestCase { + private Repository db; + + private AsIsFileService service; + + protected void setUp() throws Exception { + super.setUp(); + + db = createBareRepository(); + service = new AsIsFileService(); + } + + public void testDisabledSingleton() throws ServiceNotAuthorizedException { + service = AsIsFileService.DISABLED; + try { + service.access(new R(null, "1.2.3.4"), db); + fail("Created session for anonymous user: null"); + } catch (ServiceNotEnabledException e) { + // expected not authorized + } + + try { + service.access(new R("bob", "1.2.3.4"), db); + fail("Created session for user: \"bob\""); + } catch (ServiceNotEnabledException e) { + // expected not authorized + } + } + + public void testCreate_Default() throws ServiceNotEnabledException, + ServiceNotAuthorizedException { + service.access(new R(null, "1.2.3.4"), db); + service.access(new R("bob", "1.2.3.4"), db); + } + + public void testCreate_Disabled() throws ServiceNotAuthorizedException { + db.getConfig().setBoolean("http", null, "getanyfile", false); + + try { + service.access(new R(null, "1.2.3.4"), db); + fail("Created session for anonymous user: null"); + } catch (ServiceNotEnabledException e) { + // expected not authorized + } + + try { + service.access(new R("bob", "1.2.3.4"), db); + fail("Created session for user: \"bob\""); + } catch (ServiceNotEnabledException e) { + // expected not authorized + } + } + + public void testCreate_Enabled() throws ServiceNotEnabledException, + ServiceNotAuthorizedException { + db.getConfig().setBoolean("http", null, "getanyfile", true); + service.access(new R(null, "1.2.3.4"), db); + service.access(new R("bob", "1.2.3.4"), db); + } + + private final class R extends HttpServletRequestWrapper { + private final String user; + + private final String host; + + R(final String user, final String host) { + super(new Request() /* can't pass null, sigh */); + this.user = user; + this.host = host; + } + + @Override + public String getRemoteHost() { + return host; + } + + @Override + public String getRemoteUser() { + return user; + } + } +} diff --git a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/DefaultReceivePackFactoryTest.java b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/DefaultReceivePackFactoryTest.java new file mode 100644 index 000000000..5338caaa4 --- /dev/null +++ b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/DefaultReceivePackFactoryTest.java @@ -0,0 +1,198 @@ +/* + * Copyright (C) 2009-2010, Google Inc. + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.eclipse.jgit.http.test; + +import javax.servlet.http.HttpServletRequestWrapper; + +import org.eclipse.jetty.server.Request; +import org.eclipse.jgit.http.server.resolver.DefaultReceivePackFactory; +import org.eclipse.jgit.http.server.resolver.ReceivePackFactory; +import org.eclipse.jgit.http.server.resolver.ServiceNotAuthorizedException; +import org.eclipse.jgit.http.server.resolver.ServiceNotEnabledException; +import org.eclipse.jgit.junit.LocalDiskRepositoryTestCase; +import org.eclipse.jgit.lib.PersonIdent; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.transport.ReceivePack; + +public class DefaultReceivePackFactoryTest extends LocalDiskRepositoryTestCase { + private Repository db; + + private ReceivePackFactory factory; + + protected void setUp() throws Exception { + super.setUp(); + + db = createBareRepository(); + factory = new DefaultReceivePackFactory(); + } + + public void testDisabledSingleton() throws ServiceNotAuthorizedException { + factory = ReceivePackFactory.DISABLED; + + try { + factory.create(new R(null, "localhost"), db); + fail("Created session for anonymous user: null"); + } catch (ServiceNotEnabledException e) { + // expected not authorized + } + + try { + factory.create(new R("", "localhost"), db); + fail("Created session for anonymous user: \"\""); + } catch (ServiceNotEnabledException e) { + // expected not authorized + } + + try { + factory.create(new R("bob", "localhost"), db); + fail("Created session for user: \"bob\""); + } catch (ServiceNotEnabledException e) { + // expected not authorized + } + } + + public void testCreate_NullUser() throws ServiceNotEnabledException { + try { + factory.create(new R(null, "localhost"), db); + fail("Created session for anonymous user: null"); + } catch (ServiceNotAuthorizedException e) { + // expected not authorized + } + } + + public void testCreate_EmptyStringUser() throws ServiceNotEnabledException { + try { + factory.create(new R("", "localhost"), db); + fail("Created session for anonymous user: \"\""); + } catch (ServiceNotAuthorizedException e) { + // expected not authorized + } + } + + public void testCreate_AuthUser() throws ServiceNotEnabledException, + ServiceNotAuthorizedException { + ReceivePack rp; + rp = factory.create(new R("bob", "1.2.3.4"), db); + assertNotNull("have ReceivePack", rp); + assertSame(db, rp.getRepository()); + + PersonIdent id = rp.getRefLogIdent(); + assertNotNull(id); + assertEquals("bob", id.getName()); + assertEquals("bob@1.2.3.4", id.getEmailAddress()); + + // Should have inherited off the current system, which is mocked + assertEquals(author.getTimeZoneOffset(), id.getTimeZoneOffset()); + assertEquals(author.getWhen(), id.getWhen()); + } + + public void testCreate_Disabled() throws ServiceNotAuthorizedException { + db.getConfig().setBoolean("http", null, "receivepack", false); + + try { + factory.create(new R(null, "localhost"), db); + fail("Created session for anonymous user: null"); + } catch (ServiceNotEnabledException e) { + // expected not authorized + } + + try { + factory.create(new R("", "localhost"), db); + fail("Created session for anonymous user: \"\""); + } catch (ServiceNotEnabledException e) { + // expected not authorized + } + + try { + factory.create(new R("bob", "localhost"), db); + fail("Created session for user: \"bob\""); + } catch (ServiceNotEnabledException e) { + // expected not authorized + } + } + + public void testCreate_Enabled() throws ServiceNotEnabledException, + ServiceNotAuthorizedException { + db.getConfig().setBoolean("http", null, "receivepack", true); + ReceivePack rp; + + rp = factory.create(new R(null, "1.2.3.4"), db); + assertNotNull("have ReceivePack", rp); + assertSame(db, rp.getRepository()); + + PersonIdent id = rp.getRefLogIdent(); + assertNotNull(id); + assertEquals("anonymous", id.getName()); + assertEquals("anonymous@1.2.3.4", id.getEmailAddress()); + + // Should have inherited off the current system, which is mocked + assertEquals(author.getTimeZoneOffset(), id.getTimeZoneOffset()); + assertEquals(author.getWhen(), id.getWhen()); + + rp = factory.create(new R("bob", "1.2.3.4"), db); + assertNotNull("have ReceivePack", rp); + } + + private final class R extends HttpServletRequestWrapper { + private final String user; + + private final String host; + + R(final String user, final String host) { + super(new Request() /* can't pass null, sigh */); + this.user = user; + this.host = host; + } + + @Override + public String getRemoteHost() { + return host; + } + + @Override + public String getRemoteUser() { + return user; + } + } +} diff --git a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/DefaultUploadPackFactoryTest.java b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/DefaultUploadPackFactoryTest.java new file mode 100644 index 000000000..8f57d40af --- /dev/null +++ b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/DefaultUploadPackFactoryTest.java @@ -0,0 +1,160 @@ +/* + * Copyright (C) 2009-2010, Google Inc. + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.eclipse.jgit.http.test; + +import javax.servlet.http.HttpServletRequestWrapper; + +import org.eclipse.jetty.server.Request; +import org.eclipse.jgit.http.server.resolver.DefaultUploadPackFactory; +import org.eclipse.jgit.http.server.resolver.ServiceNotAuthorizedException; +import org.eclipse.jgit.http.server.resolver.ServiceNotEnabledException; +import org.eclipse.jgit.http.server.resolver.UploadPackFactory; +import org.eclipse.jgit.junit.LocalDiskRepositoryTestCase; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.transport.UploadPack; + +public class DefaultUploadPackFactoryTest extends LocalDiskRepositoryTestCase { + private Repository db; + + private UploadPackFactory factory; + + protected void setUp() throws Exception { + super.setUp(); + + db = createBareRepository(); + factory = new DefaultUploadPackFactory(); + } + + public void testDisabledSingleton() throws ServiceNotAuthorizedException { + factory = UploadPackFactory.DISABLED; + + try { + factory.create(new R(null, "localhost"), db); + fail("Created session for anonymous user: null"); + } catch (ServiceNotEnabledException e) { + // expected not authorized + } + + try { + factory.create(new R("", "localhost"), db); + fail("Created session for anonymous user: \"\""); + } catch (ServiceNotEnabledException e) { + // expected not authorized + } + + try { + factory.create(new R("bob", "localhost"), db); + fail("Created session for user: \"bob\""); + } catch (ServiceNotEnabledException e) { + // expected not authorized + } + } + + public void testCreate_Default() throws ServiceNotEnabledException, + ServiceNotAuthorizedException { + UploadPack up; + + up = factory.create(new R(null, "1.2.3.4"), db); + assertNotNull("have UploadPack", up); + assertSame(db, up.getRepository()); + + up = factory.create(new R("bob", "1.2.3.4"), db); + assertNotNull("have UploadPack", up); + assertSame(db, up.getRepository()); + } + + public void testCreate_Disabled() throws ServiceNotAuthorizedException { + db.getConfig().setBoolean("http", null, "uploadpack", false); + + try { + factory.create(new R(null, "localhost"), db); + fail("Created session for anonymous user: null"); + } catch (ServiceNotEnabledException e) { + // expected not authorized + } + + try { + factory.create(new R("bob", "localhost"), db); + fail("Created session for user: \"bob\""); + } catch (ServiceNotEnabledException e) { + // expected not authorized + } + } + + public void testCreate_Enabled() throws ServiceNotEnabledException, + ServiceNotAuthorizedException { + db.getConfig().setBoolean("http", null, "uploadpack", true); + UploadPack up; + + up = factory.create(new R(null, "1.2.3.4"), db); + assertNotNull("have UploadPack", up); + assertSame(db, up.getRepository()); + + up = factory.create(new R("bob", "1.2.3.4"), db); + assertNotNull("have UploadPack", up); + assertSame(db, up.getRepository()); + } + + private final class R extends HttpServletRequestWrapper { + private final String user; + + private final String host; + + R(final String user, final String host) { + super(new Request() /* can't pass null, sigh */); + this.user = user; + this.host = host; + } + + @Override + public String getRemoteHost() { + return host; + } + + @Override + public String getRemoteUser() { + return user; + } + } +} diff --git a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/DumbClientDumbServerTest.java b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/DumbClientDumbServerTest.java new file mode 100644 index 000000000..1ec324428 --- /dev/null +++ b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/DumbClientDumbServerTest.java @@ -0,0 +1,246 @@ +/* + * Copyright (C) 2010, Google Inc. + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.eclipse.jgit.http.test; + +import static org.eclipse.jgit.util.HttpSupport.HDR_ACCEPT; +import static org.eclipse.jgit.util.HttpSupport.HDR_PRAGMA; +import static org.eclipse.jgit.util.HttpSupport.HDR_USER_AGENT; + +import java.io.File; +import java.io.IOException; +import java.net.URI; +import java.util.List; +import java.util.Map; + +import org.eclipse.jetty.servlet.DefaultServlet; +import org.eclipse.jetty.servlet.ServletContextHandler; +import org.eclipse.jgit.errors.NotSupportedException; +import org.eclipse.jgit.http.test.util.AccessEvent; +import org.eclipse.jgit.http.test.util.HttpTestCase; +import org.eclipse.jgit.junit.TestRepository; +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.NullProgressMonitor; +import org.eclipse.jgit.lib.Ref; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.revwalk.RevBlob; +import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.transport.FetchConnection; +import org.eclipse.jgit.transport.HttpTransport; +import org.eclipse.jgit.transport.Transport; +import org.eclipse.jgit.transport.TransportHttp; +import org.eclipse.jgit.transport.URIish; + +public class DumbClientDumbServerTest extends HttpTestCase { + private Repository remoteRepository; + + private URIish remoteURI; + + private RevBlob A_txt; + + private RevCommit A, B; + + protected void setUp() throws Exception { + super.setUp(); + + final TestRepository src = createTestRepository(); + final File srcGit = src.getRepository().getDirectory(); + final URI base = srcGit.getParentFile().toURI(); + + ServletContextHandler app = server.addContext("/git"); + app.setResourceBase(base.toString()); + app.addServlet(DefaultServlet.class, "/"); + + server.setUp(); + + remoteRepository = src.getRepository(); + remoteURI = toURIish(app, srcGit.getName()); + + A_txt = src.blob("A"); + A = src.commit().add("A_txt", A_txt).create(); + B = src.commit().parent(A).add("A_txt", "C").add("B", "B").create(); + src.update(master, B); + } + + public void testListRemote() throws IOException { + Repository dst = createBareRepository(); + + assertEquals("http", remoteURI.getScheme()); + + Map map; + Transport t = Transport.open(dst, remoteURI); + try { + // I didn't make up these public interface names, I just + // approved them for inclusion into the code base. Sorry. + // --spearce + // + assertTrue("isa TransportHttp", t instanceof TransportHttp); + assertTrue("isa HttpTransport", t instanceof HttpTransport); + + FetchConnection c = t.openFetch(); + try { + map = c.getRefsMap(); + } finally { + c.close(); + } + } finally { + t.close(); + } + + assertNotNull("have map of refs", map); + assertEquals(2, map.size()); + + assertNotNull("has " + master, map.get(master)); + assertEquals(B, map.get(master).getObjectId()); + + assertNotNull("has " + Constants.HEAD, map.get(Constants.HEAD)); + assertEquals(B, map.get(Constants.HEAD).getObjectId()); + + List requests = getRequests(); + assertEquals(2, requests.size()); + assertEquals(0, getRequests(remoteURI, "git-upload-pack").size()); + + AccessEvent info = requests.get(0); + assertEquals("GET", info.getMethod()); + assertEquals(join(remoteURI, "info/refs"), info.getPath()); + assertEquals(1, info.getParameters().size()); + assertEquals("git-upload-pack", info.getParameter("service")); + assertEquals("no-cache", info.getRequestHeader(HDR_PRAGMA)); + assertNotNull("has user-agent", info.getRequestHeader(HDR_USER_AGENT)); + assertTrue("is jgit agent", info.getRequestHeader(HDR_USER_AGENT) + .startsWith("JGit/")); + assertEquals("application/x-git-upload-pack-advertisement, */*", info + .getRequestHeader(HDR_ACCEPT)); + assertEquals(200, info.getStatus()); + + AccessEvent head = requests.get(1); + assertEquals("GET", head.getMethod()); + assertEquals(join(remoteURI, "HEAD"), head.getPath()); + assertEquals(0, head.getParameters().size()); + assertEquals("no-cache", head.getRequestHeader(HDR_PRAGMA)); + assertNotNull("has user-agent", head.getRequestHeader(HDR_USER_AGENT)); + assertTrue("is jgit agent", head.getRequestHeader(HDR_USER_AGENT) + .startsWith("JGit/")); + assertEquals(200, head.getStatus()); + } + + public void testInitialClone_Loose() throws Exception { + Repository dst = createBareRepository(); + assertFalse(dst.hasObject(A_txt)); + + Transport t = Transport.open(dst, remoteURI); + try { + t.fetch(NullProgressMonitor.INSTANCE, mirror(master)); + } finally { + t.close(); + } + + assertTrue(dst.hasObject(A_txt)); + assertEquals(B, dst.getRef(master).getObjectId()); + fsck(dst, B); + + List loose = getRequests(loose(remoteURI, A_txt)); + assertEquals(1, loose.size()); + assertEquals("GET", loose.get(0).getMethod()); + assertEquals(0, loose.get(0).getParameters().size()); + assertEquals(200, loose.get(0).getStatus()); + } + + public void testInitialClone_Packed() throws Exception { + new TestRepository(remoteRepository).packAndPrune(); + + Repository dst = createBareRepository(); + assertFalse(dst.hasObject(A_txt)); + + Transport t = Transport.open(dst, remoteURI); + try { + t.fetch(NullProgressMonitor.INSTANCE, mirror(master)); + } finally { + t.close(); + } + + assertTrue(dst.hasObject(A_txt)); + assertEquals(B, dst.getRef(master).getObjectId()); + fsck(dst, B); + + List req; + AccessEvent event; + + req = getRequests(loose(remoteURI, B)); + assertEquals(1, req.size()); + event = req.get(0); + assertEquals("GET", event.getMethod()); + assertEquals(0, event.getParameters().size()); + assertEquals(404, event.getStatus()); + + req = getRequests(join(remoteURI, "objects/info/packs")); + assertEquals(1, req.size()); + event = req.get(0); + assertEquals("GET", event.getMethod()); + assertEquals(0, event.getParameters().size()); + assertEquals("no-cache", event.getRequestHeader(HDR_PRAGMA)); + assertNotNull("has user-agent", event.getRequestHeader(HDR_USER_AGENT)); + assertTrue("is jgit agent", event.getRequestHeader(HDR_USER_AGENT) + .startsWith("JGit/")); + assertEquals(200, event.getStatus()); + } + + public void testPushNotSupported() throws Exception { + final TestRepository src = createTestRepository(); + final RevCommit Q = src.commit().create(); + final Repository db = src.getRepository(); + + Transport t = Transport.open(db, remoteURI); + try { + try { + t.push(NullProgressMonitor.INSTANCE, push(src, Q)); + fail("push incorrectly completed against a dumb server"); + } catch (NotSupportedException nse) { + String exp = "remote does not support smart HTTP push"; + assertEquals(exp, nse.getMessage()); + } + } finally { + t.close(); + } + } +} diff --git a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/DumbClientSmartServerTest.java b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/DumbClientSmartServerTest.java new file mode 100644 index 000000000..0b28712f6 --- /dev/null +++ b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/DumbClientSmartServerTest.java @@ -0,0 +1,261 @@ +/* + * Copyright (C) 2010, Google Inc. + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.eclipse.jgit.http.test; + +import static org.eclipse.jgit.util.HttpSupport.HDR_ACCEPT; +import static org.eclipse.jgit.util.HttpSupport.HDR_CONTENT_TYPE; +import static org.eclipse.jgit.util.HttpSupport.HDR_PRAGMA; +import static org.eclipse.jgit.util.HttpSupport.HDR_USER_AGENT; + +import java.io.IOException; +import java.util.List; +import java.util.Map; + +import javax.servlet.http.HttpServletRequest; + +import org.eclipse.jetty.servlet.ServletContextHandler; +import org.eclipse.jetty.servlet.ServletHolder; +import org.eclipse.jgit.errors.NotSupportedException; +import org.eclipse.jgit.errors.RepositoryNotFoundException; +import org.eclipse.jgit.http.server.GitServlet; +import org.eclipse.jgit.http.server.resolver.RepositoryResolver; +import org.eclipse.jgit.http.server.resolver.ServiceNotEnabledException; +import org.eclipse.jgit.http.test.util.AccessEvent; +import org.eclipse.jgit.http.test.util.HttpTestCase; +import org.eclipse.jgit.junit.TestRepository; +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.NullProgressMonitor; +import org.eclipse.jgit.lib.Ref; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.revwalk.RevBlob; +import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.transport.FetchConnection; +import org.eclipse.jgit.transport.HttpTransport; +import org.eclipse.jgit.transport.Transport; +import org.eclipse.jgit.transport.TransportHttp; +import org.eclipse.jgit.transport.URIish; + +public class DumbClientSmartServerTest extends HttpTestCase { + private Repository remoteRepository; + + private URIish remoteURI; + + private RevBlob A_txt; + + private RevCommit A, B; + + protected void setUp() throws Exception { + super.setUp(); + + final TestRepository src = createTestRepository(); + final String srcName = src.getRepository().getDirectory().getName(); + + ServletContextHandler app = server.addContext("/git"); + GitServlet gs = new GitServlet(); + gs.setRepositoryResolver(new RepositoryResolver() { + public Repository open(HttpServletRequest req, String name) + throws RepositoryNotFoundException, + ServiceNotEnabledException { + if (!name.equals(srcName)) + throw new RepositoryNotFoundException(name); + + final Repository db = src.getRepository(); + db.incrementOpen(); + return db; + } + }); + app.addServlet(new ServletHolder(gs), "/*"); + + server.setUp(); + + remoteRepository = src.getRepository(); + remoteURI = toURIish(app, srcName); + + A_txt = src.blob("A"); + A = src.commit().add("A_txt", A_txt).create(); + B = src.commit().parent(A).add("A_txt", "C").add("B", "B").create(); + src.update(master, B); + } + + public void testListRemote() throws IOException { + Repository dst = createBareRepository(); + + assertEquals("http", remoteURI.getScheme()); + + Map map; + Transport t = Transport.open(dst, remoteURI); + ((TransportHttp) t).setUseSmartHttp(false); + try { + // I didn't make up these public interface names, I just + // approved them for inclusion into the code base. Sorry. + // --spearce + // + assertTrue("isa TransportHttp", t instanceof TransportHttp); + assertTrue("isa HttpTransport", t instanceof HttpTransport); + + FetchConnection c = t.openFetch(); + try { + map = c.getRefsMap(); + } finally { + c.close(); + } + } finally { + t.close(); + } + + assertNotNull("have map of refs", map); + assertEquals(2, map.size()); + + assertNotNull("has " + master, map.get(master)); + assertEquals(B, map.get(master).getObjectId()); + + assertNotNull("has " + Constants.HEAD, map.get(Constants.HEAD)); + assertEquals(B, map.get(Constants.HEAD).getObjectId()); + + List requests = getRequests(); + assertEquals(2, requests.size()); + assertEquals(0, getRequests(remoteURI, "git-upload-pack").size()); + + AccessEvent info = requests.get(0); + assertEquals("GET", info.getMethod()); + assertEquals(join(remoteURI, "info/refs"), info.getPath()); + assertEquals(0, info.getParameters().size()); + assertNull("no service parameter", info.getParameter("service")); + assertEquals("no-cache", info.getRequestHeader(HDR_PRAGMA)); + assertNotNull("has user-agent", info.getRequestHeader(HDR_USER_AGENT)); + assertTrue("is jgit agent", info.getRequestHeader(HDR_USER_AGENT) + .startsWith("JGit/")); + assertEquals("*/*", info.getRequestHeader(HDR_ACCEPT)); + assertEquals(200, info.getStatus()); + assertEquals("text/plain;charset=UTF-8", info + .getResponseHeader(HDR_CONTENT_TYPE)); + + AccessEvent head = requests.get(1); + assertEquals("GET", head.getMethod()); + assertEquals(join(remoteURI, "HEAD"), head.getPath()); + assertEquals(0, head.getParameters().size()); + assertEquals(200, head.getStatus()); + assertEquals("text/plain", head.getResponseHeader(HDR_CONTENT_TYPE)); + } + + public void testInitialClone_Small() throws Exception { + Repository dst = createBareRepository(); + assertFalse(dst.hasObject(A_txt)); + + Transport t = Transport.open(dst, remoteURI); + ((TransportHttp) t).setUseSmartHttp(false); + try { + t.fetch(NullProgressMonitor.INSTANCE, mirror(master)); + } finally { + t.close(); + } + + assertTrue(dst.hasObject(A_txt)); + assertEquals(B, dst.getRef(master).getObjectId()); + fsck(dst, B); + + List loose = getRequests(loose(remoteURI, A_txt)); + assertEquals(1, loose.size()); + assertEquals("GET", loose.get(0).getMethod()); + assertEquals(0, loose.get(0).getParameters().size()); + assertEquals(200, loose.get(0).getStatus()); + assertEquals("application/x-git-loose-object", loose.get(0) + .getResponseHeader(HDR_CONTENT_TYPE)); + } + + public void testInitialClone_Packed() throws Exception { + new TestRepository(remoteRepository).packAndPrune(); + + Repository dst = createBareRepository(); + assertFalse(dst.hasObject(A_txt)); + + Transport t = Transport.open(dst, remoteURI); + ((TransportHttp) t).setUseSmartHttp(false); + try { + t.fetch(NullProgressMonitor.INSTANCE, mirror(master)); + } finally { + t.close(); + } + + assertTrue(dst.hasObject(A_txt)); + assertEquals(B, dst.getRef(master).getObjectId()); + fsck(dst, B); + + List req; + + req = getRequests(loose(remoteURI, B)); + assertEquals(1, req.size()); + assertEquals("GET", req.get(0).getMethod()); + assertEquals(0, req.get(0).getParameters().size()); + assertEquals(404, req.get(0).getStatus()); + + req = getRequests(join(remoteURI, "objects/info/packs")); + assertEquals(1, req.size()); + assertEquals("GET", req.get(0).getMethod()); + assertEquals(0, req.get(0).getParameters().size()); + assertEquals(200, req.get(0).getStatus()); + assertEquals("text/plain;charset=UTF-8", req.get(0).getResponseHeader( + HDR_CONTENT_TYPE)); + } + + public void testPushNotSupported() throws Exception { + final TestRepository src = createTestRepository(); + final RevCommit Q = src.commit().create(); + final Repository db = src.getRepository(); + + Transport t = Transport.open(db, remoteURI); + ((TransportHttp) t).setUseSmartHttp(false); + try { + try { + t.push(NullProgressMonitor.INSTANCE, push(src, Q)); + fail("push incorrectly completed against a smart server"); + } catch (NotSupportedException nse) { + String exp = "smart HTTP push disabled"; + assertEquals(exp, nse.getMessage()); + } + } finally { + t.close(); + } + } +} diff --git a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/ErrorServletTest.java b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/ErrorServletTest.java new file mode 100644 index 000000000..18c1bb8f6 --- /dev/null +++ b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/ErrorServletTest.java @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2009-2010, Google Inc. + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.eclipse.jgit.http.test; + +import java.net.HttpURLConnection; +import java.net.URI; + +import junit.framework.TestCase; + +import org.eclipse.jetty.servlet.ServletContextHandler; +import org.eclipse.jetty.servlet.ServletHolder; +import org.eclipse.jgit.http.server.glue.ErrorServlet; +import org.eclipse.jgit.http.test.util.AppServer; + +public class ErrorServletTest extends TestCase { + private AppServer server; + + protected void setUp() throws Exception { + super.setUp(); + + server = new AppServer(); + + ServletContextHandler ctx = server.addContext("/"); + ctx.addServlet(new ServletHolder(new ErrorServlet(404)), "/404"); + ctx.addServlet(new ServletHolder(new ErrorServlet(500)), "/500"); + + server.setUp(); + } + + protected void tearDown() throws Exception { + if (server != null) { + server.tearDown(); + } + super.tearDown(); + } + + public void testHandler() throws Exception { + final URI uri = server.getURI(); + assertEquals(404, ((HttpURLConnection) uri.resolve("/404").toURL() + .openConnection()).getResponseCode()); + assertEquals(500, ((HttpURLConnection) uri.resolve("/500").toURL() + .openConnection()).getResponseCode()); + } +} diff --git a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/FileResolverTest.java b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/FileResolverTest.java new file mode 100644 index 000000000..d7ca5900f --- /dev/null +++ b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/FileResolverTest.java @@ -0,0 +1,137 @@ +/* + * Copyright (C) 2010, Google Inc. + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.eclipse.jgit.http.test; + +import java.io.File; +import java.io.IOException; + +import org.eclipse.jgit.errors.RepositoryNotFoundException; +import org.eclipse.jgit.http.server.resolver.FileResolver; +import org.eclipse.jgit.http.server.resolver.ServiceNotEnabledException; +import org.eclipse.jgit.junit.LocalDiskRepositoryTestCase; +import org.eclipse.jgit.lib.Repository; + +public class FileResolverTest extends LocalDiskRepositoryTestCase { + public void testUnreasonableNames() throws ServiceNotEnabledException { + assertUnreasonable(""); + assertUnreasonable("a\\b"); + assertUnreasonable("../b"); + assertUnreasonable("a/../b"); + assertUnreasonable("a/./b"); + assertUnreasonable("a//b"); + + if (new File("/foo").isAbsolute()) + assertUnreasonable("/foo"); + + if (new File("//server/share").isAbsolute()) + assertUnreasonable("//server/share"); + + if (new File("C:/windows").isAbsolute()) + assertUnreasonable("C:/windows"); + } + + private void assertUnreasonable(String name) + throws ServiceNotEnabledException { + FileResolver r = new FileResolver(new File("."), false); + try { + r.open(null, name); + fail("Opened unreasonable name \"" + name + "\""); + } catch (RepositoryNotFoundException e) { + assertEquals("repository not found: " + name, e.getMessage()); + assertNull("has no cause", e.getCause()); + } + } + + public void testExportOk() throws IOException { + final Repository a = createBareRepository(); + final String name = a.getDirectory().getName(); + final File base = a.getDirectory().getParentFile(); + final File export = new File(a.getDirectory(), "git-daemon-export-ok"); + FileResolver resolver; + + assertFalse("no git-daemon-export-ok", export.exists()); + resolver = new FileResolver(base, false /* require flag */); + try { + resolver.open(null, name); + fail("opened non-exported repository"); + } catch (ServiceNotEnabledException e) { + assertEquals("Service not enabled", e.getMessage()); + } + + resolver = new FileResolver(base, true /* export all */); + try { + resolver.open(null, name).close(); + } catch (ServiceNotEnabledException e) { + fail("did not honor export-all flag"); + } + + export.createNewFile(); + assertTrue("has git-daemon-export-ok", export.exists()); + resolver = new FileResolver(base, false /* require flag */); + try { + resolver.open(null, name).close(); + } catch (ServiceNotEnabledException e) { + fail("did not honor git-daemon-export-ok"); + } + } + + public void testNotAGitRepository() throws IOException, + ServiceNotEnabledException { + final Repository a = createBareRepository(); + final String name = a.getDirectory().getName() + "-not-a-git"; + final File base = a.getDirectory().getParentFile(); + FileResolver resolver = new FileResolver(base, false); + + try { + resolver.open(null, name); + } catch (RepositoryNotFoundException e) { + assertEquals("repository not found: " + name, e.getMessage()); + + Throwable why = e.getCause(); + assertNotNull("has cause", why); + assertEquals("repository not found: " + + new File(base, name).getAbsolutePath(), why.getMessage()); + } + } +} diff --git a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/GitServletInitTest.java b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/GitServletInitTest.java new file mode 100644 index 000000000..840703678 --- /dev/null +++ b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/GitServletInitTest.java @@ -0,0 +1,119 @@ +/* + * Copyright (C) 2010, Google Inc. + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.eclipse.jgit.http.test; + +import java.util.List; + +import javax.servlet.ServletException; + +import junit.framework.TestCase; + +import org.eclipse.jetty.servlet.ServletContextHandler; +import org.eclipse.jetty.servlet.ServletHolder; +import org.eclipse.jgit.http.server.GitServlet; +import org.eclipse.jgit.http.test.util.AppServer; +import org.eclipse.jgit.http.test.util.MockServletConfig; +import org.eclipse.jgit.http.test.util.RecordingLogger; + +public class GitServletInitTest extends TestCase { + private AppServer server; + + protected void tearDown() throws Exception { + if (server != null) { + server.tearDown(); + server = null; + } + super.tearDown(); + } + + public void testDefaultConstructor_NoBasePath() throws Exception { + GitServlet s = new GitServlet(); + try { + s.init(new MockServletConfig()); + fail("Init did not crash due to missing parameter"); + } catch (ServletException e) { + assertTrue(e.getMessage().contains("base-path")); + } + } + + public void testDefaultConstructor_WithBasePath() throws Exception { + MockServletConfig c = new MockServletConfig(); + c.setInitParameter("base-path", "."); + c.setInitParameter("export-all", "false"); + + GitServlet s = new GitServlet(); + s.init(c); + s.destroy(); + } + + public void testInitUnderContainer_NoBasePath() throws Exception { + server = new AppServer(); + + ServletContextHandler app = server.addContext("/"); + ServletHolder s = app.addServlet(GitServlet.class, "/git"); + s.setInitOrder(1); + + server.setUp(); + + List events = RecordingLogger.getWarnings(); + assertFalse("Servlet started without base-path", events.isEmpty()); + + Throwable why = events.get(0).getCause(); + assertTrue("Caught ServletException", why instanceof ServletException); + assertTrue("Wanted base-path", why.getMessage().contains("base-path")); + } + + public void testInitUnderContainer_WithBasePath() throws Exception { + server = new AppServer(); + + ServletContextHandler app = server.addContext("/"); + ServletHolder s = app.addServlet(GitServlet.class, "/git"); + s.setInitOrder(1); + s.setInitParameter("base-path", "."); + s.setInitParameter("export-all", "true"); + + server.setUp(); + assertTrue("no warnings", RecordingLogger.getWarnings().isEmpty()); + } +} diff --git a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/HttpClientTests.java b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/HttpClientTests.java new file mode 100644 index 000000000..729466df3 --- /dev/null +++ b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/HttpClientTests.java @@ -0,0 +1,328 @@ +/* + * Copyright (C) 2009-2010, Google Inc. + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.eclipse.jgit.http.test; + +import java.io.File; +import java.net.URI; +import java.util.List; + +import javax.servlet.http.HttpServletRequest; + +import org.eclipse.jetty.servlet.DefaultServlet; +import org.eclipse.jetty.servlet.ServletContextHandler; +import org.eclipse.jetty.servlet.ServletHolder; +import org.eclipse.jgit.errors.NoRemoteRepositoryException; +import org.eclipse.jgit.errors.RepositoryNotFoundException; +import org.eclipse.jgit.errors.TransportException; +import org.eclipse.jgit.http.server.GitServlet; +import org.eclipse.jgit.http.server.resolver.RepositoryResolver; +import org.eclipse.jgit.http.server.resolver.ServiceNotEnabledException; +import org.eclipse.jgit.http.test.util.AccessEvent; +import org.eclipse.jgit.http.test.util.HttpTestCase; +import org.eclipse.jgit.junit.TestRepository; +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.Ref; +import org.eclipse.jgit.lib.RefUpdate; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.transport.FetchConnection; +import org.eclipse.jgit.transport.Transport; +import org.eclipse.jgit.transport.URIish; + +public class HttpClientTests extends HttpTestCase { + private TestRepository remoteRepository; + + private URIish dumbAuthNoneURI; + + private URIish dumbAuthBasicURI; + + private URIish smartAuthNoneURI; + + private URIish smartAuthBasicURI; + + protected void setUp() throws Exception { + super.setUp(); + + remoteRepository = createTestRepository(); + remoteRepository.update(master, remoteRepository.commit().create()); + + ServletContextHandler dNone = dumb("/dnone"); + ServletContextHandler dBasic = server.authBasic(dumb("/dbasic")); + + ServletContextHandler sNone = smart("/snone"); + ServletContextHandler sBasic = server.authBasic(smart("/sbasic")); + + server.setUp(); + + final String srcName = nameOf(remoteRepository); + dumbAuthNoneURI = toURIish(dNone, srcName); + dumbAuthBasicURI = toURIish(dBasic, srcName); + + smartAuthNoneURI = toURIish(sNone, srcName); + smartAuthBasicURI = toURIish(sBasic, srcName); + } + + private ServletContextHandler dumb(final String path) { + final File srcGit = remoteRepository.getRepository().getDirectory(); + final URI base = srcGit.getParentFile().toURI(); + + ServletContextHandler ctx = server.addContext(path); + ctx.setResourceBase(base.toString()); + ctx.addServlet(DefaultServlet.class, "/"); + return ctx; + } + + private ServletContextHandler smart(final String path) { + GitServlet gs = new GitServlet(); + gs.setRepositoryResolver(new RepositoryResolver() { + public Repository open(HttpServletRequest req, String name) + throws RepositoryNotFoundException, + ServiceNotEnabledException { + if (!name.equals(nameOf(remoteRepository))) + throw new RepositoryNotFoundException(name); + + final Repository db = remoteRepository.getRepository(); + db.incrementOpen(); + return db; + } + }); + + ServletContextHandler ctx = server.addContext(path); + ctx.addServlet(new ServletHolder(gs), "/*"); + return ctx; + } + + private static String nameOf(final TestRepository db) { + return db.getRepository().getDirectory().getName(); + } + + public void testRepositoryNotFound_Dumb() throws Exception { + URIish uri = toURIish("/dumb.none/not-found"); + Repository dst = createBareRepository(); + Transport t = Transport.open(dst, uri); + try { + try { + t.openFetch(); + fail("connection opened to not found repository"); + } catch (NoRemoteRepositoryException err) { + String exp = uri + ": " + uri + + "/info/refs?service=git-upload-pack not found"; + assertEquals(exp, err.getMessage()); + } + } finally { + t.close(); + } + } + + public void testRepositoryNotFound_Smart() throws Exception { + URIish uri = toURIish("/smart.none/not-found"); + Repository dst = createBareRepository(); + Transport t = Transport.open(dst, uri); + try { + try { + t.openFetch(); + fail("connection opened to not found repository"); + } catch (NoRemoteRepositoryException err) { + String exp = uri + ": " + uri + + "/info/refs?service=git-upload-pack not found"; + assertEquals(exp, err.getMessage()); + } + } finally { + t.close(); + } + } + + public void testListRemote_Dumb_DetachedHEAD() throws Exception { + Repository src = remoteRepository.getRepository(); + RefUpdate u = src.updateRef(Constants.HEAD, true); + RevCommit Q = remoteRepository.commit().message("Q").create(); + u.setNewObjectId(Q); + assertEquals(RefUpdate.Result.FORCED, u.forceUpdate()); + + Repository dst = createBareRepository(); + Ref head; + Transport t = Transport.open(dst, dumbAuthNoneURI); + try { + FetchConnection c = t.openFetch(); + try { + head = c.getRef(Constants.HEAD); + } finally { + c.close(); + } + } finally { + t.close(); + } + assertNotNull("has " + Constants.HEAD, head); + assertEquals(Q, head.getObjectId()); + } + + public void testListRemote_Dumb_NoHEAD() throws Exception { + Repository src = remoteRepository.getRepository(); + File headref = new File(src.getDirectory(), Constants.HEAD); + assertTrue("HEAD used to be present", headref.delete()); + assertFalse("HEAD is gone", headref.exists()); + + Repository dst = createBareRepository(); + Ref head; + Transport t = Transport.open(dst, dumbAuthNoneURI); + try { + FetchConnection c = t.openFetch(); + try { + head = c.getRef(Constants.HEAD); + } finally { + c.close(); + } + } finally { + t.close(); + } + assertNull("has no " + Constants.HEAD, head); + } + + public void testListRemote_Smart_DetachedHEAD() throws Exception { + Repository src = remoteRepository.getRepository(); + RefUpdate u = src.updateRef(Constants.HEAD, true); + RevCommit Q = remoteRepository.commit().message("Q").create(); + u.setNewObjectId(Q); + assertEquals(RefUpdate.Result.FORCED, u.forceUpdate()); + + Repository dst = createBareRepository(); + Ref head; + Transport t = Transport.open(dst, smartAuthNoneURI); + try { + FetchConnection c = t.openFetch(); + try { + head = c.getRef(Constants.HEAD); + } finally { + c.close(); + } + } finally { + t.close(); + } + assertNotNull("has " + Constants.HEAD, head); + assertEquals(Q, head.getObjectId()); + } + + public void testListRemote_Smart_WithQueryParameters() throws Exception { + URIish myURI = toURIish("/snone/do?r=1&p=test.git"); + Repository dst = createBareRepository(); + Transport t = Transport.open(dst, myURI); + try { + try { + t.openFetch(); + fail("test did not fail to find repository as expected"); + } catch (NoRemoteRepositoryException err) { + // expected + } + } finally { + t.close(); + } + + List requests = getRequests(); + assertEquals(1, requests.size()); + + AccessEvent info = requests.get(0); + assertEquals("GET", info.getMethod()); + assertEquals("/snone/do", info.getPath()); + assertEquals(3, info.getParameters().size()); + assertEquals("1", info.getParameter("r")); + assertEquals("test.git/info/refs", info.getParameter("p")); + assertEquals("git-upload-pack", info.getParameter("service")); + assertEquals(404, info.getStatus()); + } + + public void testListRemote_Dumb_NeedsAuth() throws Exception { + Repository dst = createBareRepository(); + Transport t = Transport.open(dst, dumbAuthBasicURI); + try { + try { + t.openFetch(); + fail("connection opened even info/refs needs auth basic"); + } catch (TransportException err) { + String status = "401 Unauthorized"; + String exp = dumbAuthBasicURI + ": " + status; + assertEquals(exp, err.getMessage()); + } + } finally { + t.close(); + } + } + + public void testListRemote_Smart_UploadPackNeedsAuth() throws Exception { + Repository dst = createBareRepository(); + Transport t = Transport.open(dst, smartAuthBasicURI); + try { + try { + t.openFetch(); + fail("connection opened even though service disabled"); + } catch (TransportException err) { + String status = "401 Unauthorized"; + String exp = smartAuthBasicURI + ": " + status; + assertEquals(exp, err.getMessage()); + } + } finally { + t.close(); + } + } + + public void testListRemote_Smart_UploadPackDisabled() throws Exception { + Repository src = remoteRepository.getRepository(); + src.getConfig().setBoolean("http", null, "uploadpack", false); + src.getConfig().save(); + + Repository dst = createBareRepository(); + Transport t = Transport.open(dst, smartAuthNoneURI); + try { + try { + t.openFetch(); + fail("connection opened even though service disabled"); + } catch (TransportException err) { + String exp = smartAuthNoneURI + + ": git-upload-pack not permitted"; + assertEquals(exp, err.getMessage()); + } + } finally { + t.close(); + } + } +} diff --git a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/SmartClientSmartServerTest.java b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/SmartClientSmartServerTest.java new file mode 100644 index 000000000..fd78bb451 --- /dev/null +++ b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/SmartClientSmartServerTest.java @@ -0,0 +1,556 @@ +/* + * Copyright (C) 2009-2010, Google Inc. + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.eclipse.jgit.http.test; + +import static org.eclipse.jgit.util.HttpSupport.HDR_CONTENT_ENCODING; +import static org.eclipse.jgit.util.HttpSupport.HDR_CONTENT_LENGTH; +import static org.eclipse.jgit.util.HttpSupport.HDR_CONTENT_TYPE; + +import java.io.IOException; +import java.io.PrintWriter; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.jetty.servlet.FilterHolder; +import org.eclipse.jetty.servlet.FilterMapping; +import org.eclipse.jetty.servlet.ServletContextHandler; +import org.eclipse.jetty.servlet.ServletHolder; +import org.eclipse.jgit.errors.RepositoryNotFoundException; +import org.eclipse.jgit.errors.TransportException; +import org.eclipse.jgit.http.server.GitServlet; +import org.eclipse.jgit.http.server.resolver.RepositoryResolver; +import org.eclipse.jgit.http.server.resolver.ServiceNotEnabledException; +import org.eclipse.jgit.http.test.util.AccessEvent; +import org.eclipse.jgit.http.test.util.HttpTestCase; +import org.eclipse.jgit.junit.TestRepository; +import org.eclipse.jgit.junit.TestRng; +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.NullProgressMonitor; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.Ref; +import org.eclipse.jgit.lib.ReflogReader; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.lib.RepositoryConfig; +import org.eclipse.jgit.revwalk.RevBlob; +import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.transport.FetchConnection; +import org.eclipse.jgit.transport.HttpTransport; +import org.eclipse.jgit.transport.RemoteRefUpdate; +import org.eclipse.jgit.transport.Transport; +import org.eclipse.jgit.transport.TransportHttp; +import org.eclipse.jgit.transport.URIish; + +public class SmartClientSmartServerTest extends HttpTestCase { + private static final String HDR_TRANSFER_ENCODING = "Transfer-Encoding"; + + private Repository remoteRepository; + + private URIish remoteURI; + + private URIish brokenURI; + + private RevBlob A_txt; + + private RevCommit A, B; + + protected void setUp() throws Exception { + super.setUp(); + + final TestRepository src = createTestRepository(); + final String srcName = src.getRepository().getDirectory().getName(); + + ServletContextHandler app = server.addContext("/git"); + GitServlet gs = new GitServlet(); + gs.setRepositoryResolver(new RepositoryResolver() { + public Repository open(HttpServletRequest req, String name) + throws RepositoryNotFoundException, + ServiceNotEnabledException { + if (!name.equals(srcName)) + throw new RepositoryNotFoundException(name); + + final Repository db = src.getRepository(); + db.incrementOpen(); + return db; + } + }); + app.addServlet(new ServletHolder(gs), "/*"); + + ServletContextHandler broken = server.addContext("/bad"); + broken.addFilter(new FilterHolder(new Filter() { + public void doFilter(ServletRequest request, + ServletResponse response, FilterChain chain) + throws IOException, ServletException { + final HttpServletResponse r = (HttpServletResponse) response; + r.setContentType("text/plain"); + r.setCharacterEncoding("UTF-8"); + PrintWriter w = r.getWriter(); + w.print("OK"); + w.close(); + } + + public void init(FilterConfig filterConfig) throws ServletException { + // + } + + public void destroy() { + // + } + }), "/" + srcName + "/git-upload-pack", FilterMapping.DEFAULT); + broken.addServlet(new ServletHolder(gs), "/*"); + + server.setUp(); + + remoteRepository = src.getRepository(); + remoteURI = toURIish(app, srcName); + brokenURI = toURIish(broken, srcName); + + A_txt = src.blob("A"); + A = src.commit().add("A_txt", A_txt).create(); + B = src.commit().parent(A).add("A_txt", "C").add("B", "B").create(); + src.update(master, B); + + src.update("refs/garbage/a/very/long/ref/name/to/compress", B); + } + + public void testListRemote() throws IOException { + Repository dst = createBareRepository(); + + assertEquals("http", remoteURI.getScheme()); + + Map map; + Transport t = Transport.open(dst, remoteURI); + try { + // I didn't make up these public interface names, I just + // approved them for inclusion into the code base. Sorry. + // --spearce + // + assertTrue("isa TransportHttp", t instanceof TransportHttp); + assertTrue("isa HttpTransport", t instanceof HttpTransport); + + FetchConnection c = t.openFetch(); + try { + map = c.getRefsMap(); + } finally { + c.close(); + } + } finally { + t.close(); + } + + assertNotNull("have map of refs", map); + assertEquals(3, map.size()); + + assertNotNull("has " + master, map.get(master)); + assertEquals(B, map.get(master).getObjectId()); + + assertNotNull("has " + Constants.HEAD, map.get(Constants.HEAD)); + assertEquals(B, map.get(Constants.HEAD).getObjectId()); + + List requests = getRequests(); + assertEquals(1, requests.size()); + + AccessEvent info = requests.get(0); + assertEquals("GET", info.getMethod()); + assertEquals(join(remoteURI, "info/refs"), info.getPath()); + assertEquals(1, info.getParameters().size()); + assertEquals("git-upload-pack", info.getParameter("service")); + assertEquals(200, info.getStatus()); + assertEquals("application/x-git-upload-pack-advertisement", info + .getResponseHeader(HDR_CONTENT_TYPE)); + assertEquals("gzip", info.getResponseHeader(HDR_CONTENT_ENCODING)); + } + + public void testInitialClone_Small() throws Exception { + Repository dst = createBareRepository(); + assertFalse(dst.hasObject(A_txt)); + + Transport t = Transport.open(dst, remoteURI); + try { + t.fetch(NullProgressMonitor.INSTANCE, mirror(master)); + } finally { + t.close(); + } + + assertTrue(dst.hasObject(A_txt)); + assertEquals(B, dst.getRef(master).getObjectId()); + fsck(dst, B); + + List requests = getRequests(); + assertEquals(2, requests.size()); + + AccessEvent info = requests.get(0); + assertEquals("GET", info.getMethod()); + assertEquals(join(remoteURI, "info/refs"), info.getPath()); + assertEquals(1, info.getParameters().size()); + assertEquals("git-upload-pack", info.getParameter("service")); + assertEquals(200, info.getStatus()); + assertEquals("application/x-git-upload-pack-advertisement", info + .getResponseHeader(HDR_CONTENT_TYPE)); + assertEquals("gzip", info.getResponseHeader(HDR_CONTENT_ENCODING)); + + AccessEvent service = requests.get(1); + assertEquals("POST", service.getMethod()); + assertEquals(join(remoteURI, "git-upload-pack"), service.getPath()); + assertEquals(0, service.getParameters().size()); + assertNotNull("has content-length", service + .getRequestHeader(HDR_CONTENT_LENGTH)); + assertNull("not chunked", service + .getRequestHeader(HDR_TRANSFER_ENCODING)); + assertNull("no compression (too small)", service + .getRequestHeader(HDR_CONTENT_ENCODING)); + + assertEquals(200, service.getStatus()); + assertEquals("application/x-git-upload-pack-result", service + .getResponseHeader(HDR_CONTENT_TYPE)); + assertNull("no compression (never compressed)", service + .getResponseHeader(HDR_CONTENT_ENCODING)); + } + + public void testFetchUpdateExisting() throws Exception { + // Bootstrap by doing the clone. + // + TestRepository dst = createTestRepository(); + Transport t = Transport.open(dst.getRepository(), remoteURI); + try { + t.fetch(NullProgressMonitor.INSTANCE, mirror(master)); + } finally { + t.close(); + } + assertEquals(B, dst.getRepository().getRef(master).getObjectId()); + List cloneRequests = getRequests(); + + // Force enough into the local client that enumeration will + // need multiple packets, but not too many to overflow and + // not pick up the ACK_COMMON message. + // + TestRepository.BranchBuilder b = dst.branch(master); + for (int i = 0; i < 32 - 1; i++) + b.commit().tick(3600 /* 1 hour */).message("c" + i).create(); + + // Create a new commit on the remote. + // + b = new TestRepository(remoteRepository).branch(master); + RevCommit Z = b.commit().message("Z").create(); + + // Now incrementally update. + // + t = Transport.open(dst.getRepository(), remoteURI); + try { + t.fetch(NullProgressMonitor.INSTANCE, mirror(master)); + } finally { + t.close(); + } + assertEquals(Z, dst.getRepository().getRef(master).getObjectId()); + + List requests = getRequests(); + requests.removeAll(cloneRequests); + assertEquals(3, requests.size()); + + AccessEvent info = requests.get(0); + assertEquals("GET", info.getMethod()); + assertEquals(join(remoteURI, "info/refs"), info.getPath()); + assertEquals(1, info.getParameters().size()); + assertEquals("git-upload-pack", info.getParameter("service")); + assertEquals(200, info.getStatus()); + assertEquals("application/x-git-upload-pack-advertisement", info + .getResponseHeader(HDR_CONTENT_TYPE)); + + // We should have needed two requests to perform the fetch + // due to the high number of local unknown commits. + // + AccessEvent service = requests.get(1); + assertEquals("POST", service.getMethod()); + assertEquals(join(remoteURI, "git-upload-pack"), service.getPath()); + assertEquals(0, service.getParameters().size()); + assertNotNull("has content-length", service + .getRequestHeader(HDR_CONTENT_LENGTH)); + assertNull("not chunked", service + .getRequestHeader(HDR_TRANSFER_ENCODING)); + + assertEquals(200, service.getStatus()); + assertEquals("application/x-git-upload-pack-result", service + .getResponseHeader(HDR_CONTENT_TYPE)); + + service = requests.get(2); + assertEquals("POST", service.getMethod()); + assertEquals(join(remoteURI, "git-upload-pack"), service.getPath()); + assertEquals(0, service.getParameters().size()); + assertNotNull("has content-length", service + .getRequestHeader(HDR_CONTENT_LENGTH)); + assertNull("not chunked", service + .getRequestHeader(HDR_TRANSFER_ENCODING)); + + assertEquals(200, service.getStatus()); + assertEquals("application/x-git-upload-pack-result", service + .getResponseHeader(HDR_CONTENT_TYPE)); + } + + public void testInitialClone_BrokenServer() throws Exception { + Repository dst = createBareRepository(); + assertFalse(dst.hasObject(A_txt)); + + Transport t = Transport.open(dst, brokenURI); + try { + try { + t.fetch(NullProgressMonitor.INSTANCE, mirror(master)); + fail("fetch completed despite upload-pack being broken"); + } catch (TransportException err) { + String exp = brokenURI + ": expected" + + " Content-Type application/x-git-upload-pack-result;" + + " received Content-Type text/plain;charset=UTF-8"; + assertEquals(exp, err.getMessage()); + } + } finally { + t.close(); + } + + List requests = getRequests(); + assertEquals(2, requests.size()); + + AccessEvent info = requests.get(0); + assertEquals("GET", info.getMethod()); + assertEquals(join(brokenURI, "info/refs"), info.getPath()); + assertEquals(1, info.getParameters().size()); + assertEquals("git-upload-pack", info.getParameter("service")); + assertEquals(200, info.getStatus()); + assertEquals("application/x-git-upload-pack-advertisement", info + .getResponseHeader(HDR_CONTENT_TYPE)); + + AccessEvent service = requests.get(1); + assertEquals("POST", service.getMethod()); + assertEquals(join(brokenURI, "git-upload-pack"), service.getPath()); + assertEquals(0, service.getParameters().size()); + assertEquals(200, service.getStatus()); + assertEquals("text/plain;charset=UTF-8", service + .getResponseHeader(HDR_CONTENT_TYPE)); + } + + public void testPush_NotAuthorized() throws Exception { + final TestRepository src = createTestRepository(); + final RevBlob Q_txt = src.blob("new text"); + final RevCommit Q = src.commit().add("Q", Q_txt).create(); + final Repository db = src.getRepository(); + final String dstName = Constants.R_HEADS + "new.branch"; + Transport t; + + // push anonymous shouldn't be allowed. + // + t = Transport.open(db, remoteURI); + try { + final String srcExpr = Q.name(); + final boolean forceUpdate = false; + final String localName = null; + final ObjectId oldId = null; + + RemoteRefUpdate u = new RemoteRefUpdate(src.getRepository(), + srcExpr, dstName, forceUpdate, localName, oldId); + try { + t.push(NullProgressMonitor.INSTANCE, Collections.singleton(u)); + fail("anonymous push incorrectly accepted without error"); + } catch (TransportException e) { + final String status = "401 Unauthorized"; + final String exp = remoteURI.toString() + ": " + status; + assertEquals(exp, e.getMessage()); + } + } finally { + t.close(); + } + + List requests = getRequests(); + assertEquals(1, requests.size()); + + AccessEvent info = requests.get(0); + assertEquals("GET", info.getMethod()); + assertEquals(join(remoteURI, "info/refs"), info.getPath()); + assertEquals(1, info.getParameters().size()); + assertEquals("git-receive-pack", info.getParameter("service")); + assertEquals(401, info.getStatus()); + } + + public void testPush_CreateBranch() throws Exception { + final TestRepository src = createTestRepository(); + final RevBlob Q_txt = src.blob("new text"); + final RevCommit Q = src.commit().add("Q", Q_txt).create(); + final Repository db = src.getRepository(); + final String dstName = Constants.R_HEADS + "new.branch"; + Transport t; + + enableReceivePack(); + + t = Transport.open(db, remoteURI); + try { + final String srcExpr = Q.name(); + final boolean forceUpdate = false; + final String localName = null; + final ObjectId oldId = null; + + RemoteRefUpdate u = new RemoteRefUpdate(src.getRepository(), + srcExpr, dstName, forceUpdate, localName, oldId); + t.push(NullProgressMonitor.INSTANCE, Collections.singleton(u)); + } finally { + t.close(); + } + + assertTrue(remoteRepository.hasObject(Q_txt)); + assertNotNull("has " + dstName, remoteRepository.getRef(dstName)); + assertEquals(Q, remoteRepository.getRef(dstName).getObjectId()); + fsck(remoteRepository, Q); + + final ReflogReader log = remoteRepository.getReflogReader(dstName); + assertNotNull("has log for " + dstName); + + final ReflogReader.Entry last = log.getLastEntry(); + assertNotNull("has last entry", last); + assertEquals(ObjectId.zeroId(), last.getOldId()); + assertEquals(Q, last.getNewId()); + assertEquals("anonymous", last.getWho().getName()); + + // Assumption: The host name we use to contact the server should + // be the server's own host name, because it should be the loopback + // network interface. + // + final String clientHost = remoteURI.getHost(); + assertEquals("anonymous@" + clientHost, last.getWho().getEmailAddress()); + assertEquals("push: created", last.getComment()); + + List requests = getRequests(); + assertEquals(2, requests.size()); + + AccessEvent info = requests.get(0); + assertEquals("GET", info.getMethod()); + assertEquals(join(remoteURI, "info/refs"), info.getPath()); + assertEquals(1, info.getParameters().size()); + assertEquals("git-receive-pack", info.getParameter("service")); + assertEquals(200, info.getStatus()); + assertEquals("application/x-git-receive-pack-advertisement", info + .getResponseHeader(HDR_CONTENT_TYPE)); + + AccessEvent service = requests.get(1); + assertEquals("POST", service.getMethod()); + assertEquals(join(remoteURI, "git-receive-pack"), service.getPath()); + assertEquals(0, service.getParameters().size()); + assertNotNull("has content-length", service + .getRequestHeader(HDR_CONTENT_LENGTH)); + assertNull("not chunked", service + .getRequestHeader(HDR_TRANSFER_ENCODING)); + + assertEquals(200, service.getStatus()); + assertEquals("application/x-git-receive-pack-result", service + .getResponseHeader(HDR_CONTENT_TYPE)); + } + + public void testPush_ChunkedEncoding() throws Exception { + final TestRepository src = createTestRepository(); + final RevBlob Q_bin = src.blob(new TestRng("Q").nextBytes(128 * 1024)); + final RevCommit Q = src.commit().add("Q", Q_bin).create(); + final Repository db = src.getRepository(); + final String dstName = Constants.R_HEADS + "new.branch"; + Transport t; + + enableReceivePack(); + + db.getConfig().setInt("core", null, "compression", 0); + db.getConfig().setInt("http", null, "postbuffer", 8 * 1024); + db.getConfig().save(); + + t = Transport.open(db, remoteURI); + try { + final String srcExpr = Q.name(); + final boolean forceUpdate = false; + final String localName = null; + final ObjectId oldId = null; + + RemoteRefUpdate u = new RemoteRefUpdate(src.getRepository(), + srcExpr, dstName, forceUpdate, localName, oldId); + t.push(NullProgressMonitor.INSTANCE, Collections.singleton(u)); + } finally { + t.close(); + } + + assertTrue(remoteRepository.hasObject(Q_bin)); + assertNotNull("has " + dstName, remoteRepository.getRef(dstName)); + assertEquals(Q, remoteRepository.getRef(dstName).getObjectId()); + fsck(remoteRepository, Q); + + List requests = getRequests(); + assertEquals(2, requests.size()); + + AccessEvent info = requests.get(0); + assertEquals("GET", info.getMethod()); + assertEquals(join(remoteURI, "info/refs"), info.getPath()); + assertEquals(1, info.getParameters().size()); + assertEquals("git-receive-pack", info.getParameter("service")); + assertEquals(200, info.getStatus()); + assertEquals("application/x-git-receive-pack-advertisement", info + .getResponseHeader(HDR_CONTENT_TYPE)); + + AccessEvent service = requests.get(1); + assertEquals("POST", service.getMethod()); + assertEquals(join(remoteURI, "git-receive-pack"), service.getPath()); + assertEquals(0, service.getParameters().size()); + assertNull("no content-length", service + .getRequestHeader(HDR_CONTENT_LENGTH)); + assertEquals("chunked", service.getRequestHeader(HDR_TRANSFER_ENCODING)); + + assertEquals(200, service.getStatus()); + assertEquals("application/x-git-receive-pack-result", service + .getResponseHeader(HDR_CONTENT_TYPE)); + } + + private void enableReceivePack() throws IOException { + final RepositoryConfig cfg = remoteRepository.getConfig(); + cfg.setBoolean("http", null, "receivepack", true); + cfg.save(); + } +} diff --git a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/util/AccessEvent.java b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/util/AccessEvent.java new file mode 100644 index 000000000..c6d531e5b --- /dev/null +++ b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/util/AccessEvent.java @@ -0,0 +1,181 @@ +/* + * Copyright (C) 2010, Google Inc. + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.eclipse.jgit.http.test.util; + +import java.util.Collections; +import java.util.Enumeration; +import java.util.Map; +import java.util.TreeMap; + +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.Response; + +/** A single request made through {@link AppServer}. */ +public class AccessEvent { + private final String method; + + private final String uri; + + private final Map requestHeaders; + + private final Map parameters; + + private final int status; + + private final Map responseHeaders; + + AccessEvent(final Request req, final Response rsp) { + method = req.getMethod(); + uri = req.getRequestURI(); + requestHeaders = cloneHeaders(req); + parameters = clone(req.getParameterMap()); + + status = rsp.getStatus(); + responseHeaders = cloneHeaders(rsp); + } + + private static Map cloneHeaders(final Request req) { + Map r = new TreeMap(); + Enumeration hn = req.getHeaderNames(); + while (hn.hasMoreElements()) { + String key = (String) hn.nextElement(); + if (!r.containsKey(key)) { + r.put(key, req.getHeader(key)); + } + } + return Collections.unmodifiableMap(r); + } + + private static Map cloneHeaders(final Response rsp) { + Map r = new TreeMap(); + Enumeration hn = rsp.getHttpFields().getFieldNames(); + while (hn.hasMoreElements()) { + String key = hn.nextElement(); + if (!r.containsKey(key)) { + Enumeration v = rsp.getHttpFields().getValues(key); + r.put(key, v.nextElement()); + } + } + return Collections.unmodifiableMap(r); + } + + @SuppressWarnings("unchecked") + private static Map clone(Map parameterMap) { + return new TreeMap(parameterMap); + } + + /** @return {@code "GET"} or {@code "POST"} */ + public String getMethod() { + return method; + } + + /** @return path of the file on the server, e.g. {@code /git/HEAD}. */ + public String getPath() { + return uri; + } + + /** + * @param name + * name of the request header to read. + * @return first value of the request header; null if not sent. + */ + public String getRequestHeader(String name) { + return requestHeaders.get(name); + } + + /** + * @param name + * name of the request parameter to read. + * @return first value of the request parameter; null if not sent. + */ + public String getParameter(String name) { + String[] r = parameters.get(name); + return r != null && 1 <= r.length ? r[0] : null; + } + + /** @return all parameters in the request. */ + public Map getParameters() { + return parameters; + } + + /** @return HTTP status code of the response, e.g. 200, 403, 500. */ + public int getStatus() { + return status; + } + + /** + * @param name + * name of the response header to read. + * @return first value of the response header; null if not sent. + */ + public String getResponseHeader(String name) { + return responseHeaders.get(name); + } + + public String toString() { + StringBuilder b = new StringBuilder(); + b.append(method); + b.append(' '); + b.append(uri); + if (!parameters.isEmpty()) { + b.append('?'); + boolean first = true; + for (Map.Entry e : parameters.entrySet()) { + for (String val : e.getValue()) { + if (!first) { + b.append('&'); + } + first = false; + + b.append(e.getKey()); + b.append('='); + b.append(val); + } + } + } + b.append(' '); + b.append(status); + return b.toString(); + } +} diff --git a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/util/AppServer.java b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/util/AppServer.java new file mode 100644 index 000000000..74df7086e --- /dev/null +++ b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/util/AppServer.java @@ -0,0 +1,295 @@ +/* + * Copyright (C) 2010, Google Inc. + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.eclipse.jgit.http.test.util; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.UnknownHostException; +import java.util.ArrayList; +import java.util.List; + +import junit.framework.Assert; + +import org.eclipse.jetty.http.security.Constraint; +import org.eclipse.jetty.http.security.Password; +import org.eclipse.jetty.security.Authenticator; +import org.eclipse.jetty.security.ConstraintMapping; +import org.eclipse.jetty.security.ConstraintSecurityHandler; +import org.eclipse.jetty.security.MappedLoginService; +import org.eclipse.jetty.security.authentication.BasicAuthenticator; +import org.eclipse.jetty.server.Connector; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.UserIdentity; +import org.eclipse.jetty.server.handler.ContextHandlerCollection; +import org.eclipse.jetty.server.handler.RequestLogHandler; +import org.eclipse.jetty.server.nio.SelectChannelConnector; +import org.eclipse.jetty.servlet.ServletContextHandler; +import org.eclipse.jetty.util.thread.QueuedThreadPool; +import org.eclipse.jgit.transport.URIish; + +/** + * Tiny web application server for unit testing. + *

+ * Tests should start the server in their {@code setUp()} method and stop the + * server in their {@code tearDown()} method. Only while started the server's + * URL and/or port number can be obtained. + */ +public class AppServer { + /** Realm name for the secure access areas. */ + public static final String realm = "Secure Area"; + + /** Username for secured access areas. */ + public static final String username = "agitter"; + + /** Password for {@link #username} in secured access areas. */ + public static final String password = "letmein"; + + static { + // Install a logger that throws warning messages. + // + final String prop = "org.eclipse.jetty.util.log.class"; + System.setProperty(prop, RecordingLogger.class.getName()); + } + + private final Server server; + + private final Connector connector; + + private final ContextHandlerCollection contexts; + + private final TestRequestLog log; + + public AppServer() { + connector = new SelectChannelConnector(); + connector.setPort(0); + try { + final InetAddress me = InetAddress.getByName("localhost"); + connector.setHost(me.getHostAddress()); + } catch (UnknownHostException e) { + throw new RuntimeException("Cannot find localhost", e); + } + + // We need a handful of threads in the thread pool, otherwise + // our tests will deadlock when they can't open enough requests. + // In theory we only need 1 concurrent connection at a time, but + // I suspect the JRE isn't doing request pipelining on existing + // connections like we want it to. + // + final QueuedThreadPool pool = new QueuedThreadPool(); + pool.setMinThreads(1); + pool.setMaxThreads(4); + pool.setMaxQueued(8); + + contexts = new ContextHandlerCollection(); + + log = new TestRequestLog(); + + final RequestLogHandler logHandler = new RequestLogHandler(); + logHandler.setHandler(contexts); + logHandler.setRequestLog(log); + + server = new Server(); + server.setConnectors(new Connector[] { connector }); + server.setThreadPool(pool); + server.setHandler(logHandler); + + server.setStopAtShutdown(false); + server.setGracefulShutdown(0); + } + + /** + * Create a new servlet context within the server. + *

+ * This method should be invoked before the server is started, once for each + * context the caller wants to register. + * + * @param path + * path of the context; use "/" for the root context if binding + * to the root is desired. + * @return the context to add servlets into. + */ + public ServletContextHandler addContext(String path) { + assertNotYetSetUp(); + if ("".equals(path)) + path = "/"; + + ServletContextHandler ctx = new ServletContextHandler(); + ctx.setContextPath(path); + contexts.addHandler(ctx); + + return ctx; + } + + public ServletContextHandler authBasic(ServletContextHandler ctx) { + assertNotYetSetUp(); + auth(ctx, new BasicAuthenticator()); + return ctx; + } + + private void auth(ServletContextHandler ctx, Authenticator authType) { + final String role = "can-access"; + + MappedLoginService users = new MappedLoginService() { + @Override + protected UserIdentity loadUser(String who) { + return null; + } + + @Override + protected void loadUsers() throws IOException { + putUser(username, new Password(password), new String[] { role }); + } + }; + + ConstraintMapping cm = new ConstraintMapping(); + cm.setConstraint(new Constraint()); + cm.getConstraint().setAuthenticate(true); + cm.getConstraint().setDataConstraint(Constraint.DC_NONE); + cm.getConstraint().setRoles(new String[] { role }); + cm.setPathSpec("/*"); + + ConstraintSecurityHandler sec = new ConstraintSecurityHandler(); + sec.setStrict(false); + sec.setRealmName(realm); + sec.setAuthenticator(authType); + sec.setLoginService(users); + sec.setConstraintMappings(new ConstraintMapping[] { cm }); + sec.setHandler(ctx); + + contexts.removeHandler(ctx); + contexts.addHandler(sec); + } + + /** + * Start the server on a random local port. + * + * @throws Exception + * the server cannot be started, testing is not possible. + */ + public void setUp() throws Exception { + RecordingLogger.clear(); + log.clear(); + server.start(); + } + + /** + * Shutdown the server. + * + * @throws Exception + * the server refuses to halt, or wasn't running. + */ + public void tearDown() throws Exception { + RecordingLogger.clear(); + log.clear(); + server.stop(); + } + + /** + * Get the URI to reference this server. + *

+ * The returned URI includes the proper host name and port number, but does + * not contain a path. + * + * @return URI to reference this server's root context. + */ + public URI getURI() { + assertAlreadySetUp(); + String host = connector.getHost(); + if (host.contains(":") && !host.startsWith("[")) + host = "[" + host + "]"; + final String uri = "http://" + host + ":" + getPort(); + try { + return new URI(uri); + } catch (URISyntaxException e) { + throw new RuntimeException("Unexpected URI error on " + uri, e); + } + } + + /** @return the local port number the server is listening on. */ + public int getPort() { + assertAlreadySetUp(); + return ((SelectChannelConnector) connector).getLocalPort(); + } + + /** @return all requests since the server was started. */ + public List getRequests() { + return new ArrayList(log.getEvents()); + } + + /** + * @param base + * base URI used to access the server. + * @param path + * the path to locate requests for, relative to {@code base}. + * @return all requests which match the given path. + */ + public List getRequests(URIish base, String path) { + return getRequests(HttpTestCase.join(base, path)); + } + + /** + * @param path + * the path to locate requests for. + * @return all requests which match the given path. + */ + public List getRequests(String path) { + ArrayList r = new ArrayList(); + for (AccessEvent event : log.getEvents()) { + if (event.getPath().equals(path)) { + r.add(event); + } + } + return r; + } + + private void assertNotYetSetUp() { + Assert.assertFalse("server is not running", server.isRunning()); + } + + private void assertAlreadySetUp() { + Assert.assertTrue("server is running", server.isRunning()); + } +} diff --git a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/util/HttpTestCase.java b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/util/HttpTestCase.java new file mode 100644 index 000000000..e25975761 --- /dev/null +++ b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/util/HttpTestCase.java @@ -0,0 +1,161 @@ +/* + * Copyright (C) 2009-2010, Google Inc. + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.eclipse.jgit.http.test.util; + +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.eclipse.jetty.servlet.ServletContextHandler; +import org.eclipse.jgit.junit.LocalDiskRepositoryTestCase; +import org.eclipse.jgit.junit.TestRepository; +import org.eclipse.jgit.lib.AnyObjectId; +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.revwalk.RevObject; +import org.eclipse.jgit.transport.RefSpec; +import org.eclipse.jgit.transport.RemoteRefUpdate; +import org.eclipse.jgit.transport.URIish; + +/** Base class for HTTP related transport testing. */ +public abstract class HttpTestCase extends LocalDiskRepositoryTestCase { + protected static final String master = Constants.R_HEADS + Constants.MASTER; + + /** In-memory application server; subclass must start. */ + protected AppServer server; + + protected void setUp() throws Exception { + super.setUp(); + server = new AppServer(); + } + + protected void tearDown() throws Exception { + server.tearDown(); + super.tearDown(); + } + + protected TestRepository createTestRepository() throws Exception { + return new TestRepository(createBareRepository()); + } + + protected URIish toURIish(String path) throws URISyntaxException { + URI u = server.getURI().resolve(path); + return new URIish(u.toString()); + } + + protected URIish toURIish(ServletContextHandler app, String name) + throws URISyntaxException { + String p = app.getContextPath(); + if (!p.endsWith("/") && !name.startsWith("/")) + p += "/"; + p += name; + return toURIish(p); + } + + protected List getRequests() { + return server.getRequests(); + } + + protected List getRequests(URIish base, String path) { + return server.getRequests(base, path); + } + + protected List getRequests(String path) { + return server.getRequests(path); + } + + protected static void fsck(Repository db, RevObject... tips) + throws Exception { + new TestRepository(db).fsck(tips); + } + + protected static Set mirror(String... refs) { + HashSet r = new HashSet(); + for (String name : refs) { + RefSpec rs = new RefSpec(name); + rs = rs.setDestination(name); + rs = rs.setForceUpdate(true); + r.add(rs); + } + return r; + } + + protected static Collection push(TestRepository from, + RevCommit q) throws IOException { + final Repository db = from.getRepository(); + final String srcExpr = q.name(); + final String dstName = master; + final boolean forceUpdate = true; + final String localName = null; + final ObjectId oldId = null; + + RemoteRefUpdate u = new RemoteRefUpdate(db, srcExpr, dstName, + forceUpdate, localName, oldId); + return Collections.singleton(u); + } + + public static String loose(URIish base, AnyObjectId id) { + final String objectName = id.name(); + final String d = objectName.substring(0, 2); + final String f = objectName.substring(2); + return join(base, "objects/" + d + "/" + f); + } + + public static String join(URIish base, String path) { + if (path.startsWith("/")) + fail("Cannot join absolute path " + path + " to URIish " + base); + + String dir = base.getPath(); + if (!dir.endsWith("/")) + dir += "/"; + return dir + path; + } +} diff --git a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/util/MockServletConfig.java b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/util/MockServletConfig.java new file mode 100644 index 000000000..cf6f6f6a8 --- /dev/null +++ b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/util/MockServletConfig.java @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2010, Google Inc. + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.eclipse.jgit.http.test.util; + +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; + +import javax.servlet.ServletConfig; +import javax.servlet.ServletContext; + +public class MockServletConfig implements ServletConfig { + private final Map parameters = new HashMap(); + + public void setInitParameter(String name, String value) { + parameters.put(name, value); + } + + public String getInitParameter(String name) { + return parameters.get(name); + } + + public Enumeration getInitParameterNames() { + final Iterator i = parameters.keySet().iterator(); + return new Enumeration() { + public boolean hasMoreElements() { + return i.hasNext(); + } + + public String nextElement() { + return i.next(); + } + }; + } + + public String getServletName() { + return "MOCK_SERVLET"; + } + + public ServletContext getServletContext() { + return null; + } +} \ No newline at end of file diff --git a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/util/RecordingLogger.java b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/util/RecordingLogger.java new file mode 100644 index 000000000..24e125c22 --- /dev/null +++ b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/util/RecordingLogger.java @@ -0,0 +1,146 @@ +/* + * Copyright (C) 2010, Google Inc. + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.eclipse.jgit.http.test.util; + +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.eclipse.jetty.util.log.Logger; + +/** Logs warnings into an array for later inspection. */ +public class RecordingLogger implements Logger { + private static List warnings = new ArrayList(); + + /** Clear the warnings, automatically done by {@link AppServer#setUp()} */ + public static void clear() { + synchronized (warnings) { + warnings.clear(); + } + } + + /** @return the warnings (if any) from the last execution */ + public static List getWarnings() { + synchronized (warnings) { + ArrayList copy = new ArrayList(warnings); + return Collections.unmodifiableList(copy); + } + } + + @SuppressWarnings("serial") + public static class Warning extends Exception { + public Warning(String msg) { + super(msg); + } + + public Warning(String msg, Throwable cause) { + super(msg, cause); + } + } + + private final String name; + + public RecordingLogger() { + this(""); + } + + public RecordingLogger(final String name) { + this.name = name; + } + + public Logger getLogger(@SuppressWarnings("hiding") String name) { + return new RecordingLogger(name); + } + + public String getName() { + return name; + } + + public void warn(String msg, Object arg0, Object arg1) { + synchronized (warnings) { + warnings.add(new Warning(MessageFormat.format(msg, arg0, arg1))); + } + } + + public void warn(String msg, Throwable th) { + synchronized (warnings) { + warnings.add(new Warning(msg, th)); + } + } + + public void warn(String msg) { + synchronized (warnings) { + warnings.add(new Warning(msg)); + } + } + + public void debug(String msg, Object arg0, Object arg1) { + // Ignore (not relevant to test failures) + } + + public void debug(String msg, Throwable th) { + // Ignore (not relevant to test failures) + } + + public void debug(String msg) { + // Ignore (not relevant to test failures) + } + + public void info(String msg, Object arg0, Object arg1) { + // Ignore (not relevant to test failures) + } + + public void info(String msg) { + // Ignore (not relevant to test failures) + } + + public boolean isDebugEnabled() { + return false; + } + + public void setDebugEnabled(boolean enabled) { + // Ignore (not relevant to test failures) + } +} diff --git a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/util/TestRequestLog.java b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/util/TestRequestLog.java new file mode 100644 index 000000000..904f6aac8 --- /dev/null +++ b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/util/TestRequestLog.java @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2010, Google Inc. + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.eclipse.jgit.http.test.util; + +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.RequestLog; +import org.eclipse.jetty.server.Response; +import org.eclipse.jetty.util.component.AbstractLifeCycle; + +/** Logs request made through {@link AppServer}. */ +class TestRequestLog extends AbstractLifeCycle implements RequestLog { + private final List events = new ArrayList(); + + /** Reset the log back to its original empty state. */ + synchronized void clear() { + events.clear(); + } + + /** @return all of the events made since the last clear. */ + synchronized List getEvents() { + return events; + } + + public synchronized void log(Request request, Response response) { + events.add(new AccessEvent(request, response)); + } +} diff --git a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/LocalDiskRepositoryTestCase.java b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/LocalDiskRepositoryTestCase.java index ec9b1d7ac..ddace0df2 100644 --- a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/LocalDiskRepositoryTestCase.java +++ b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/LocalDiskRepositoryTestCase.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009, Google Inc. + * Copyright (C) 2009-2010, Google Inc. * Copyright (C) 2008, Robin Rosenberg * Copyright (C) 2007, Shawn O. Pearce * and other copyright owners as documented in the project's IP log. @@ -57,8 +57,10 @@ import java.util.List; import java.util.Map; import java.util.concurrent.TimeUnit; +import junit.framework.Assert; import junit.framework.TestCase; +import org.eclipse.jgit.lib.AnyObjectId; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.FileBasedConfig; import org.eclipse.jgit.lib.PersonIdent; @@ -404,6 +406,14 @@ public abstract class LocalDiskRepositoryTestCase extends TestCase { return new String(body, 0, body.length, "UTF-8"); } + protected static void assertEquals(AnyObjectId exp, AnyObjectId act) { + if (exp != null) + exp = exp.copy(); + if (act != null) + act = act.copy(); + Assert.assertEquals(exp, act); + } + private static String[] toEnvArray(final Map env) { final String[] envp = new String[env.size()]; int i = 0; diff --git a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/TestRepository.java b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/TestRepository.java index ce8b3e6ee..e738276bd 100644 --- a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/TestRepository.java +++ b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/TestRepository.java @@ -44,11 +44,15 @@ package org.eclipse.jgit.junit; import java.io.File; +import java.io.FileOutputStream; import java.io.IOException; +import java.security.MessageDigest; import java.util.ArrayList; import java.util.Collections; import java.util.Date; +import java.util.HashSet; import java.util.List; +import java.util.Set; import junit.framework.Assert; import junit.framework.AssertionFailedError; @@ -60,21 +64,30 @@ import org.eclipse.jgit.dircache.DirCacheEntry; import org.eclipse.jgit.dircache.DirCacheEditor.DeletePath; import org.eclipse.jgit.dircache.DirCacheEditor.DeleteTree; import org.eclipse.jgit.dircache.DirCacheEditor.PathEdit; +import org.eclipse.jgit.errors.IncorrectObjectTypeException; +import org.eclipse.jgit.errors.MissingObjectException; import org.eclipse.jgit.errors.ObjectWritingException; import org.eclipse.jgit.lib.AnyObjectId; import org.eclipse.jgit.lib.Commit; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.FileMode; import org.eclipse.jgit.lib.LockFile; +import org.eclipse.jgit.lib.NullProgressMonitor; +import org.eclipse.jgit.lib.ObjectChecker; +import org.eclipse.jgit.lib.ObjectDatabase; import org.eclipse.jgit.lib.ObjectDirectory; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectWriter; +import org.eclipse.jgit.lib.PackFile; +import org.eclipse.jgit.lib.PackWriter; import org.eclipse.jgit.lib.PersonIdent; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.RefUpdate; import org.eclipse.jgit.lib.RefWriter; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.lib.Tag; +import org.eclipse.jgit.lib.PackIndex.MutableEntry; +import org.eclipse.jgit.revwalk.ObjectWalk; import org.eclipse.jgit.revwalk.RevBlob; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevObject; @@ -428,26 +441,25 @@ public class TestRepository { * @throws Exception */ public void updateServerInfo() throws Exception { - if (db.getObjectDatabase() instanceof ObjectDirectory) { + final ObjectDatabase odb = db.getObjectDatabase(); + if (odb instanceof ObjectDirectory) { RefWriter rw = new RefWriter(db.getAllRefs().values()) { @Override protected void writeFile(final String name, final byte[] bin) throws IOException { - final File p = new File(db.getDirectory(), name); - final LockFile lck = new LockFile(p); - if (!lck.lock()) - throw new ObjectWritingException("Can't write " + p); - try { - lck.write(bin); - } catch (IOException ioe) { - throw new ObjectWritingException("Can't write " + p); - } - if (!lck.commit()) - throw new ObjectWritingException("Can't write " + p); + TestRepository.this.writeFile(name, bin); } }; rw.writePackedRefs(); rw.writeInfoRefs(); + + final StringBuilder w = new StringBuilder(); + for (PackFile p : ((ObjectDirectory) odb).getPacks()) { + w.append("P "); + w.append(p.getPackFile().getName()); + w.append('\n'); + } + writeFile("objects/info/packs", Constants.encodeASCII(w.toString())); } } @@ -484,6 +496,132 @@ public class TestRepository { return new BranchBuilder(ref); } + /** + * Run consistency checks against the object database. + *

+ * This method completes silently if the checks pass. A temporary revision + * pool is constructed during the checking. + * + * @param tips + * the tips to start checking from; if not supplied the refs of + * the repository are used instead. + * @throws MissingObjectException + * @throws IncorrectObjectTypeException + * @throws IOException + */ + public void fsck(RevObject... tips) throws MissingObjectException, + IncorrectObjectTypeException, IOException { + ObjectWalk ow = new ObjectWalk(db); + if (tips.length != 0) { + for (RevObject o : tips) + ow.markStart(ow.parseAny(o)); + } else { + for (Ref r : db.getAllRefs().values()) + ow.markStart(ow.parseAny(r.getObjectId())); + } + + ObjectChecker oc = new ObjectChecker(); + for (;;) { + final RevCommit o = ow.next(); + if (o == null) + break; + + final byte[] bin = db.openObject(o).getCachedBytes(); + oc.checkCommit(bin); + assertHash(o, bin); + } + + for (;;) { + final RevObject o = ow.nextObject(); + if (o == null) + break; + + final byte[] bin = db.openObject(o).getCachedBytes(); + oc.check(o.getType(), bin); + assertHash(o, bin); + } + } + + private static void assertHash(RevObject id, byte[] bin) { + MessageDigest md = Constants.newMessageDigest(); + md.update(Constants.encodedTypeString(id.getType())); + md.update((byte) ' '); + md.update(Constants.encodeASCII(bin.length)); + md.update((byte) 0); + md.update(bin); + Assert.assertEquals(id.copy(), ObjectId.fromRaw(md.digest())); + } + + /** + * Pack all reachable objects in the repository into a single pack file. + *

+ * All loose objects are automatically pruned. Existing packs however are + * not removed. + * + * @throws Exception + */ + public void packAndPrune() throws Exception { + final ObjectDirectory odb = (ObjectDirectory) db.getObjectDatabase(); + final PackWriter pw = new PackWriter(db, NullProgressMonitor.INSTANCE); + + Set all = new HashSet(); + for (Ref r : db.getAllRefs().values()) + all.add(r.getObjectId()); + pw.preparePack(all, Collections. emptySet()); + + final ObjectId name = pw.computeName(); + FileOutputStream out; + + final File pack = nameFor(odb, name, ".pack"); + out = new FileOutputStream(pack); + try { + pw.writePack(out); + } finally { + out.close(); + } + pack.setReadOnly(); + + final File idx = nameFor(odb, name, ".idx"); + out = new FileOutputStream(idx); + try { + pw.writeIndex(out); + } finally { + out.close(); + } + idx.setReadOnly(); + + odb.openPack(pack, idx); + updateServerInfo(); + prunePacked(odb); + } + + private void prunePacked(ObjectDirectory odb) { + for (PackFile p : odb.getPacks()) { + for (MutableEntry e : p) + odb.fileFor(e.toObjectId()).delete(); + } + } + + private static File nameFor(ObjectDirectory odb, ObjectId name, String t) { + File packdir = new File(odb.getDirectory(), "pack"); + return new File(packdir, "pack-" + name.name() + t); + } + + private void writeFile(final String name, final byte[] bin) + throws IOException, ObjectWritingException { + final File p = new File(db.getDirectory(), name); + final LockFile lck = new LockFile(p); + if (!lck.lock()) + throw new ObjectWritingException("Can't write " + p); + try { + lck.write(bin); + } catch (IOException ioe) { + throw new ObjectWritingException("Can't write " + p); + } + if (!lck.commit()) + throw new ObjectWritingException("Can't write " + p); + } + /** Helper to build a branch with one or more commits */ public class BranchBuilder { private final String ref; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportHttp.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportHttp.java index 62303de9f..c521e8051 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportHttp.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportHttp.java @@ -155,6 +155,8 @@ public class TransportHttp extends HttpTransport implements WalkTransport, private final ProxySelector proxySelector; + private boolean useSmartHttp = true; + TransportHttp(final Repository local, final URIish uri) throws NotSupportedException { super(local, uri); @@ -171,6 +173,20 @@ public class TransportHttp extends HttpTransport implements WalkTransport, proxySelector = ProxySelector.getDefault(); } + /** + * Toggle whether or not smart HTTP transport should be used. + *

+ * This flag exists primarily to support backwards compatibility testing + * within a testing framework, there is no need to modify it in most + * applications. + * + * @param on + * if {@code true} (default), smart HTTP is enabled. + */ + public void setUseSmartHttp(final boolean on) { + useSmartHttp = on; + } + @Override public FetchConnection openFetch() throws TransportException, NotSupportedException { @@ -271,6 +287,10 @@ public class TransportHttp extends HttpTransport implements WalkTransport, readSmartHeaders(in, service); return new SmartHttpPushConnection(in); + } else if (!useSmartHttp) { + final String msg = "smart HTTP push disabled"; + throw new NotSupportedException(msg); + } else { final String msg = "remote does not support smart HTTP push"; throw new NotSupportedException(msg); @@ -303,9 +323,11 @@ public class TransportHttp extends HttpTransport implements WalkTransport, b.append('/'); b.append(Constants.INFO_REFS); - b.append(b.indexOf("?") < 0 ? '?' : '&'); - b.append("service="); - b.append(service); + if (useSmartHttp) { + b.append(b.indexOf("?") < 0 ? '?' : '&'); + b.append("service="); + b.append(service); + } u = new URL(b.toString()); } catch (MalformedURLException e) { @@ -314,8 +336,12 @@ public class TransportHttp extends HttpTransport implements WalkTransport, try { final HttpURLConnection conn = httpOpen(u); - String expType = "application/x-" + service + "-advertisement"; - conn.setRequestProperty(HDR_ACCEPT, expType + ", */*"); + if (useSmartHttp) { + String expType = "application/x-" + service + "-advertisement"; + conn.setRequestProperty(HDR_ACCEPT, expType + ", */*"); + } else { + conn.setRequestProperty(HDR_ACCEPT, "*/*"); + } final int status = HttpSupport.response(conn); switch (status) { case HttpURLConnection.HTTP_OK: diff --git a/pom.xml b/pom.xml index d3467204f..d02cafbbe 100644 --- a/pom.xml +++ b/pom.xml @@ -138,6 +138,8 @@ CQ 3565 2.5 + + 7.0.1.v20091125 @@ -265,6 +267,12 @@ servlet-api ${servlet-api-version} + + + org.eclipse.jetty + jetty-servlet + ${jetty-version} + @@ -298,6 +306,8 @@ org.eclipse.jgit.http.server org.eclipse.jgit.pgm org.eclipse.jgit.junit + org.eclipse.jgit.test + org.eclipse.jgit.http.test