com.googlesource.gerrit.plugins.supermanifest.RepoSuperManifestIT.java Source code

Java tutorial

Introduction

Here is the source code for com.googlesource.gerrit.plugins.supermanifest.RepoSuperManifestIT.java

Source

// Copyright (C) 2016 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package com.googlesource.gerrit.plugins.supermanifest;

import static com.google.common.truth.Truth.assertThat;
import static java.nio.charset.StandardCharsets.UTF_8;

import com.google.gerrit.acceptance.GitUtil;
import com.google.gerrit.acceptance.LightweightPluginDaemonTest;
import com.google.gerrit.acceptance.PushOneCommit;
import com.google.gerrit.acceptance.PushOneCommit.Result;
import com.google.gerrit.acceptance.RestResponse;
import com.google.gerrit.acceptance.TestPlugin;
import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
import com.google.gerrit.extensions.api.projects.BranchApi;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.client.RefNames;
import com.google.inject.Inject;
import java.net.URI;
import java.util.Arrays;
import org.apache.commons.lang.RandomStringUtils;
import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
import org.eclipse.jgit.junit.TestRepository;
import org.eclipse.jgit.lib.BlobBasedConfig;
import org.eclipse.jgit.lib.Config;
import org.junit.Test;

@TestPlugin(name = "supermanifest", sysModule = "com.googlesource.gerrit.plugins.supermanifest.SuperManifestModule")
public class RepoSuperManifestIT extends LightweightPluginDaemonTest {
    Project.NameKey[] testRepoKeys;
    String[] testRepoCommits;

    @Inject
    private ProjectOperations projectOperations;

    void setupTestRepos(String prefix) throws Exception {
        testRepoKeys = new Project.NameKey[2];
        testRepoCommits = new String[2];
        for (int i = 0; i < 2; i++) {
            testRepoKeys[i] = projectOperations.newProject()
                    .name(RandomStringUtils.randomAlphabetic(8) + prefix + i).create();

            TestRepository<InMemoryRepository> repo = cloneProject(testRepoKeys[i], admin);

            PushOneCommit push = pushFactory.create(admin.getIdent(), repo, "Subject", "file" + i, "file");

            Result r = push.to("refs/heads/master");
            r.assertOkStatus();
            testRepoCommits[i] = r.getCommit().getName();
        }
    }

    void pushConfig(String config) throws Exception {
        // This will trigger a configuration reload.
        TestRepository<InMemoryRepository> allProjectRepo = cloneProject(allProjects, admin);
        GitUtil.fetch(allProjectRepo, RefNames.REFS_CONFIG + ":config");
        allProjectRepo.reset("config");
        PushOneCommit push = pushFactory.create(admin.getIdent(), allProjectRepo, "Subject", "supermanifest.config",
                config);
        PushOneCommit.Result res = push.to("refs/meta/config");
        res.assertOkStatus();
    }

    @Test
    public void basicFunctionalityWorks() throws Exception {
        setupTestRepos("project");

        // Make sure the manifest exists so the configuration loads successfully.
        Project.NameKey manifestKey = projectOperations.newProject().name(name("manifest")).create();
        TestRepository<InMemoryRepository> manifestRepo = cloneProject(manifestKey, admin);

        Project.NameKey superKey = projectOperations.newProject().name(name("superproject")).create();
        cloneProject(superKey, admin);

        pushConfig("[superproject \"" + superKey.get() + ":refs/heads/destbranch\"]\n" + "  srcRepo = "
                + manifestKey.get() + "\n" + "  srcRef = refs/heads/srcbranch\n" + "  srcPath = default.xml\n");

        String remoteXml = "  <remote name=\"origin\" fetch=\"" + canonicalWebUrl.get() + "\" />\n";
        String defaultXml = "  <default remote=\"origin\" revision=\"refs/heads/master\" />\n";
        // XML change will trigger commit to superproject.
        String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" + "<manifest>\n" + remoteXml + defaultXml
                + "  <project name=\"" + testRepoKeys[0].get() + "\" path=\"project1\" />\n" + "</manifest>\n";

        pushFactory.create(admin.getIdent(), manifestRepo, "Subject", "default.xml", xml).to("refs/heads/srcbranch")
                .assertOkStatus();

        BranchApi branch = gApi.projects().name(superKey.get()).branch("refs/heads/destbranch");
        assertThat(branch.file("project1").getContentType()).isEqualTo("x-git/gitlink; charset=UTF-8");
        try {
            branch.file("project2");
            fail("wanted exception");
        } catch (ResourceNotFoundException e) {
            // all fine.
        }

        xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" + "<manifest>\n" + remoteXml + defaultXml
                + "  <project name=\"" + testRepoKeys[0].get() + "\" path=\"project1\" />\n" + "  <project name=\""
                + testRepoKeys[1].get() + "\" path=\"project2\" />\n" + "</manifest>\n";

        pushFactory.create(admin.getIdent(), manifestRepo, "Subject", "default.xml", xml).to("refs/heads/srcbranch")
                .assertOkStatus();

        branch = gApi.projects().name(superKey.get()).branch("refs/heads/destbranch");
        assertThat(branch.file("project2").getContentType()).isEqualTo("x-git/gitlink; charset=UTF-8");

        // Make sure config change gets picked up.
        pushConfig("[superproject \"" + superKey.get() + ":refs/heads/other\"]\n" + "  srcRepo = "
                + manifestKey.get() + "\n" + "  srcRef = refs/heads/srcbranch\n" + "  srcPath = default.xml\n");

        // Push another XML change; this should trigger a commit using the new config.
        xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" + "<manifest>\n" + remoteXml + defaultXml
                + "  <project name=\"" + testRepoKeys[1].get() + "\" path=\"project3\" />\n" + "</manifest>\n";

        pushFactory.create(admin.getIdent(), manifestRepo, "Subject", "default.xml", xml).to("refs/heads/srcbranch")
                .assertOkStatus();

        branch = gApi.projects().name(superKey.get()).branch("refs/heads/other");
        assertThat(branch.file("project3").getContentType()).isEqualTo("x-git/gitlink; charset=UTF-8");
    }

    @Test
    public void httpEndpoint() throws Exception {
        setupTestRepos("project");

        // Make sure the manifest exists so the configuration loads successfully.
        Project.NameKey manifestKey = projectOperations.newProject().name(name("manifest")).create();
        TestRepository<InMemoryRepository> manifestRepo = cloneProject(manifestKey, admin);

        Project.NameKey superKey = projectOperations.newProject().name(name("superproject")).create();
        cloneProject(superKey, admin);

        String remoteXml = "  <remote name=\"origin\" fetch=\"" + canonicalWebUrl.get() + "\" />\n";
        String defaultXml = "  <default remote=\"origin\" revision=\"refs/heads/master\" />\n";
        String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" + "<manifest>\n" + remoteXml + defaultXml
                + "  <project name=\"" + testRepoKeys[0].get() + "\" path=\"project1\" />\n" + "</manifest>\n";

        pushFactory.create(admin.getIdent(), manifestRepo, "Subject", "default.xml", xml).to("refs/heads/srcbranch")
                .assertOkStatus();

        // Push config after XML. Needs a manual trigger to create the destination.
        pushConfig("[superproject \"" + superKey.get() + ":refs/heads/destbranch\"]\n" + "  srcRepo = "
                + manifestKey.get() + "\n" + "  srcRef = refs/heads/srcbranch\n" + "  srcPath = default.xml\n");

        RestResponse r = userRestSession.post("/projects/" + manifestKey + "/branches/srcbranch/update_manifest");
        r.assertForbidden();
        r = adminRestSession.post("/projects/" + manifestKey + "/branches/srcbranch/update_manifest");
        r.assertNoContent();

        BranchApi branch = gApi.projects().name(superKey.get()).branch("refs/heads/destbranch");
        assertThat(branch.file("project1").getContentType()).isEqualTo("x-git/gitlink; charset=UTF-8");
    }

    @Test
    public void rawSha1Ref() throws Exception {
        setupTestRepos("project");

        // Make sure the manifest exists so the configuration loads successfully.
        Project.NameKey manifestKey = projectOperations.newProject().name(name("manifest")).create();
        TestRepository<InMemoryRepository> manifestRepo = cloneProject(manifestKey, admin);

        Project.NameKey superKey = projectOperations.newProject().name(name("superproject")).create();
        cloneProject(superKey, admin);

        String remoteXml = "  <remote name=\"origin\" fetch=\"" + canonicalWebUrl.get() + "\" />\n";
        String defaultXml = "  <default remote=\"origin\" revision=\"refs/heads/master\" />\n";
        String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" + "<manifest>\n" + remoteXml + defaultXml
                + "  <project name=\"" + testRepoKeys[0].get() + "\" path=\"project1\"" + " revision=\""
                + testRepoCommits[0] + "\" />\n" + "</manifest>\n";

        pushFactory.create(admin.getIdent(), manifestRepo, "Subject", "default.xml", xml).to("refs/heads/srcbranch")
                .assertOkStatus();

        // Push config after XML. Needs a manual trigger to create the destination.
        pushConfig("[superproject \"" + superKey.get() + ":refs/heads/destbranch\"]\n" + "  srcRepo = "
                + manifestKey.get() + "\n" + "  srcRef = refs/heads/srcbranch\n" + "  srcPath = default.xml\n"
                + "  ignoreRemoteFailures = true\n" + "");

        { // Advance head, but the manifest refers to the previous one.
            TestRepository<InMemoryRepository> repo = cloneProject(testRepoKeys[0], admin);
            PushOneCommit push = pushFactory.create(admin.getIdent(), repo, "Subject", "file3", "file");

            Result r = push.to("refs/heads/master");
            r.assertOkStatus();
        }

        adminRestSession.post("/projects/" + manifestKey + "/branches/srcbranch/update_manifest").assertNoContent();

        BranchApi branch = gApi.projects().name(superKey.get()).branch("refs/heads/destbranch");
        assertThat(branch.file("project1").getContentType()).isEqualTo("x-git/gitlink; charset=UTF-8");
        assertThat(branch.file("project1").asString()).isEqualTo(testRepoCommits[0]);
    }

    @Test
    public void testIgnoreRemoteFailure() throws Exception {
        setupTestRepos("project");

        // Make sure the manifest exists so the configuration loads successfully.
        Project.NameKey manifestKey = projectOperations.newProject().name(name("manifest")).create();
        TestRepository<InMemoryRepository> manifestRepo = cloneProject(manifestKey, admin);

        Project.NameKey superKey = projectOperations.newProject().name(name("superproject")).create();
        cloneProject(superKey, admin);

        String remoteXml = "  <remote name=\"origin\" fetch=\"" + canonicalWebUrl.get() + "\" />\n";
        String defaultXml = "  <default remote=\"origin\" revision=\"refs/heads/master\" />\n";
        String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" + "<manifest>\n" + remoteXml
                + " <remote fetch=\"https://example.invalid/\" name=\"invalid\" /> " + defaultXml
                + "  <project name=\"" + testRepoKeys[0].get() + "\" path=\"project1\" />\n"
                + "  <project name=\"unavailable\" remote=\"invalid\" path=\"invalid\" />" + "</manifest>\n";

        pushFactory.create(admin.getIdent(), manifestRepo, "Subject", "default.xml", xml).to("refs/heads/srcbranch")
                .assertOkStatus();

        // Push config after XML. Needs a manual trigger to create the destination.
        pushConfig("[superproject \"" + superKey.get() + ":refs/heads/destbranch\"]\n" + "  srcRepo = "
                + manifestKey.get() + "\n" + "  srcRef = refs/heads/srcbranch\n" + "  srcPath = default.xml\n");

        RestResponse r = adminRestSession.post("/projects/" + manifestKey + "/branches/srcbranch/update_manifest");
        r.assertStatus(HttpStatus.INTERNAL_SERVER_ERROR_500);

        pushConfig("[superproject \"" + superKey.get() + ":refs/heads/destbranch\"]\n" + "  srcRepo = "
                + manifestKey.get() + "\n" + "  srcRef = refs/heads/srcbranch\n" + "  srcPath = default.xml\n"
                + "  ignoreRemoteFailures = true\n");

        r = adminRestSession.post("/projects/" + manifestKey + "/branches/srcbranch/update_manifest");
        r.assertNoContent();

        BranchApi branch = gApi.projects().name(superKey.get()).branch("refs/heads/destbranch");
        assertThat(branch.file("project1").getContentType()).isEqualTo("x-git/gitlink; charset=UTF-8");
    }

    private void outer() throws Exception {
        inner();
    }

    private void inner() {
        throw new IllegalStateException();
    }

    private void innerTest() throws Exception {
        try {
            outer();
            fail("should throw");
        } catch (IllegalStateException e) {
            StackTraceElement[] trimmed = SuperManifestRefUpdatedListener.trimStack(e.getStackTrace(),
                    Thread.currentThread().getStackTrace()[1]);
            String str = Arrays.toString(trimmed);
            assertThat(str).doesNotContain("trimStackTrace");
            assertThat(str).contains("innerTest");
        }
    }

    @Test
    public void trimStackTrace() throws Exception {
        innerTest();
    }

    @Test
    public void wildcardDestBranchWorks() throws Exception {
        setupTestRepos("project");

        // Make sure the manifest exists so the configuration loads successfully.
        Project.NameKey manifestKey = projectOperations.newProject().name(name("manifest")).create();
        TestRepository<InMemoryRepository> manifestRepo = cloneProject(manifestKey, admin);

        Project.NameKey superKey = projectOperations.newProject().name(name("superproject")).create();
        cloneProject(superKey, admin);

        pushConfig("[superproject \"" + superKey.get() + ":refs/heads/*\"]\n" + "  srcRepo = " + manifestKey.get()
                + "\n" + "  srcRef = blablabla\n" + "  srcPath = default.xml\n");

        String remoteXml = "  <remote name=\"origin\" fetch=\"" + canonicalWebUrl.get() + "\" />\n";
        String originXml = "  <default remote=\"origin\" revision=\"refs/heads/master\" />\n";

        // XML change will trigger commit to superproject.
        String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" + "<manifest>\n" + remoteXml + originXml
                + "  <project name=\"" + testRepoKeys[0].get() + "\" path=\"project1\" />\n" + "</manifest>\n";

        pushFactory.create(admin.getIdent(), manifestRepo, "Subject", "default.xml", xml).to("refs/heads/src1")
                .assertOkStatus();

        xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" + "<manifest>\n" + remoteXml + originXml
                + "  <project name=\"" + testRepoKeys[1].get() + "\" path=\"project2\" />\n" + "</manifest>\n";

        pushFactory.create(admin.getIdent(), manifestRepo, "Subject", "default.xml", xml).to("refs/heads/src2")
                .assertOkStatus();

        BranchApi branch1 = gApi.projects().name(superKey.get()).branch("refs/heads/src1");
        assertThat(branch1.file("project1").getContentType()).isEqualTo("x-git/gitlink; charset=UTF-8");
        try {
            branch1.file("project2");
            fail("wanted exception");
        } catch (ResourceNotFoundException e) {
            // all fine.
        }

        BranchApi branch2 = gApi.projects().name(superKey.get()).branch("refs/heads/src2");
        assertThat(branch2.file("project2").getContentType()).isEqualTo("x-git/gitlink; charset=UTF-8");
        try {
            branch2.file("project1");
            fail("wanted exception");
        } catch (ResourceNotFoundException e) {
            // all fine.
        }
    }

    @Test
    public void manifestIncludesOtherManifest() throws Exception {
        setupTestRepos("project");

        String remoteXml = "  <remote name=\"origin\" fetch=\"" + canonicalWebUrl.get() + "\" />\n";
        String originXml = "  <default remote=\"origin\" revision=\"refs/heads/master\" />\n";
        String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" + "<manifest>\n" + remoteXml + originXml
                + "  <project name=\"" + testRepoKeys[0].get() + "\" path=\"project1\" />\n" + "</manifest>\n";

        Project.NameKey manifestKey = projectOperations.newProject().name(name("manifest")).create();
        TestRepository<InMemoryRepository> manifestRepo = cloneProject(manifestKey, admin);
        pushFactory.create(admin.getIdent(), manifestRepo, "Subject", "default.xml", xml).to("refs/heads/master")
                .assertOkStatus();

        String superXml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" + "<manifest>"
                + "  <include name=\"default.xml\"/>" + "</manifest>";

        pushFactory.create(admin.getIdent(), manifestRepo, "Subject", "super.xml", superXml).to("refs/heads/master")
                .assertOkStatus();

        Project.NameKey superKey = projectOperations.newProject().name(name("superproject")).create();
        cloneProject(superKey, admin);

        pushConfig("[superproject \"" + superKey.get() + ":refs/heads/master\"]\n" + "  srcRepo = "
                + manifestKey.get() + "\n" + "  srcRef = refs/heads/master\n" + "  srcPath = super.xml\n");

        // Push a change to the source branch. We intentionally change the included XML file
        // (rather than the one mentioned in srcPath), to double check that we don't try to be too
        // smart about eliding nops.
        pushFactory.create(admin.getIdent(), manifestRepo, "Subject", "default.xml", xml + " ")
                .to("refs/heads/master").assertOkStatus();

        BranchApi branch = gApi.projects().name(superKey.get()).branch("refs/heads/master");
        assertThat(branch.file("project1").getContentType()).isEqualTo("x-git/gitlink; charset=UTF-8");
    }

    @Test
    public void relativeFetch() throws Exception {
        // Test the setup that Android uses, where the "fetch" field is relative to the location of the
        // manifest repo.
        setupTestRepos("platform/project");

        // The test framework adds more cruft to the prefix.
        String realPrefix = testRepoKeys[0].get().split("/")[0];

        Project.NameKey manifestKey = projectOperations.newProject().name(name(realPrefix + "/manifest")).create();
        TestRepository<InMemoryRepository> manifestRepo = cloneProject(manifestKey, admin);

        Project.NameKey superKey = projectOperations.newProject().name(name("superproject")).create();
        pushConfig("[superproject \"" + superKey.get() + ":refs/heads/destbranch\"]\n" + "  srcRepo = "
                + manifestKey.get() + "\n" + "  srcRef = refs/heads/srcbranch\n" + "  srcPath = default.xml\n");

        String url = canonicalWebUrl.get();
        String remoteXml = "  <remote name=\"origin\" fetch=\"..\" review=\"" + url + "\" />\n";
        String defaultXml = "  <default remote=\"origin\" revision=\"refs/heads/master\" />\n";

        String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" + "<manifest>\n" + remoteXml + defaultXml
                + "  <project name=\"" + testRepoKeys[0].get() + "\" path=\"path1\" />\n" + "</manifest>\n";
        pushFactory.create(admin.getIdent(), manifestRepo, "Subject", "default.xml", xml).to("refs/heads/srcbranch")
                .assertOkStatus();

        BranchApi branch = gApi.projects().name(superKey.get()).branch("refs/heads/destbranch");
        assertThat(branch.file("path1").getContentType()).isEqualTo("x-git/gitlink; charset=UTF-8");

        Config base = new Config();
        String gitmodule = branch.file(".gitmodules").asString();
        BlobBasedConfig cfg = new BlobBasedConfig(base, gitmodule.getBytes(UTF_8));

        String subUrl = cfg.getString("submodule", testRepoKeys[0].get(), "url");

        // URL is valid.
        URI.create(subUrl);

        // The suburls must be interpreted as relative to the parent project as a directory, i.e.
        // to go from superproject/ to platform/project0, you have to do ../platform/project0

        // URL is clean.
        assertThat(subUrl).isEqualTo("../" + realPrefix + "/project0");
    }

    @Test
    public void testToRepoKey() {
        URI base = URI.create("https://gerrit-review.googlesource.com");
        assertThat(
                SuperManifestRefUpdatedListener.urlToRepoKey(base, "https://gerrit-review.googlesource.com/repo"))
                        .isEqualTo("repo");
        assertThat(SuperManifestRefUpdatedListener.urlToRepoKey(base, "repo")).isEqualTo("repo");
        assertThat(
                SuperManifestRefUpdatedListener.urlToRepoKey(URI.create("https://gerrit-review.googlesource.com/"),
                        "https://gerrit-review.googlesource.com/repo")).isEqualTo("repo");
    }

    // TODO - should add tests for all the error handling in configuration parsing?
}