/* * The MIT License * * Copyright (c) 2017 CloudBees, Inc. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. * */ package integration; import com.cloudbees.hudson.plugins.folder.ChildNameGenerator; import hudson.model.Job; import hudson.model.TopLevelItem; import integration.harness.BasicMultiBranchProjectFactory; import java.io.File; import java.io.IOException; import java.util.Collections; import java.util.HashMap; import java.util.Map; import jenkins.branch.MultiBranchProject; import jenkins.branch.OrganizationFolder; import jenkins.scm.impl.mock.MockSCMController; import jenkins.scm.impl.mock.MockSCMNavigator; import org.apache.commons.io.FileUtils; import org.apache.commons.io.IOUtils; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Rule; import org.junit.Test; import org.junit.runners.model.Statement; import org.jvnet.hudson.test.RestartableJenkinsRule; import org.jvnet.hudson.test.recipes.LocalData; import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.instanceOf; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.notNullValue; import static org.junit.Assert.assertThat; public class MigrationTest { private static MockSCMController c; @Rule public RestartableJenkinsRule r = new RestartableJenkinsRule(); @BeforeClass public static void setupSCM() throws IOException { c = MockSCMController.recreate("ee708b58-864e-415c-a8fe-f5e458cda8d9"); c.createRepository("test.example.com"); c.createRepository("Éireann"); c.createRepository("Россия"); c.createRepository("中国"); c.createRepository("España"); c.createRepository("대한민국"); c.createBranch("test.example.com", "feature: welcome"); c.createBranch("test.example.com", "feature: browsing/part 1"); c.createBranch("test.example.com", "feature: browsing/part 2"); c.createBranch("Éireann", "gné/nua"); c.createBranch("Россия", "особенность/новый"); c.createBranch("中国", "特征/新"); c.createBranch("España", "característica/nuevo"); c.createBranch("대한민국", "특색/새로운"); } @AfterClass public static void closeSCM() { IOUtils.closeQuietly(c); c = null; } /** * Checks that data is migrated correctly from 1.x to current version. */ @Test @LocalData public void nameMangling() throws Exception { r.addStep(new Statement() { @Override public void evaluate() throws Throwable { TopLevelItem foo = r.j.jenkins.getItem("foo"); assertDataMigrated(foo); } }); r.addStep(new Statement() { @Override public void evaluate() throws Throwable { TopLevelItem foo = r.j.jenkins.getItem("foo"); assertDataMigrated(foo); } }); } /** * Checks that data migrated from 1.x to 2.0.0 name mangling is still valid when re-migrated to 2.0.2 name mangling */ @Test @LocalData public void nameMangling_2() throws Exception { r.addStep(new Statement() { @Override public void evaluate() throws Throwable { TopLevelItem foo = r.j.jenkins.getItem("foo"); assertDataMigrated(foo); } }); r.addStep(new Statement() { @Override public void evaluate() throws Throwable { TopLevelItem foo = r.j.jenkins.getItem("foo"); assertDataMigrated(foo); } }); } @Test public void createdFromScratch() throws Exception { r.addStep(new Statement() { @Override public void evaluate() throws Throwable { OrganizationFolder foo = r.j.createProject(OrganizationFolder.class, "foo"); foo.getSCMNavigators().add(new MockSCMNavigator(c, true, false, false)); foo.getProjectFactories() .replaceBy(Collections.singletonList(new BasicMultiBranchProjectFactory(null))); foo.scheduleBuild2(0).getFuture().get(); r.j.waitUntilNoActivity(); assertDataMigrated(foo); } }); r.addStep(new Statement() { @Override public void evaluate() throws Throwable { TopLevelItem foo = r.j.jenkins.getItem("foo"); assertDataMigrated(foo); } }); } /** * Checks that data is migrated correctly from 1.x to current version. */ @Test @LocalData public void nameMangling_full_reload() throws Exception { r.addStep(new Statement() { @Override public void evaluate() throws Throwable { TopLevelItem foo = r.j.jenkins.getItem("foo"); r.j.jenkins.reload(); assertDataMigrated(foo); } }); r.addStep(new Statement() { @Override public void evaluate() throws Throwable { TopLevelItem foo = r.j.jenkins.getItem("foo"); assertDataMigrated(foo); } }); } /** * Checks that data migrated from 1.x to 2.0.0 name mangling is still valid when re-migrated to 2.0.2 name mangling */ @Test @LocalData public void nameMangling_2_full_reload() throws Exception { r.addStep(new Statement() { @Override public void evaluate() throws Throwable { TopLevelItem foo = r.j.jenkins.getItem("foo"); r.j.jenkins.reload(); assertDataMigrated(foo); } }); r.addStep(new Statement() { @Override public void evaluate() throws Throwable { TopLevelItem foo = r.j.jenkins.getItem("foo"); assertDataMigrated(foo); } }); } @Test public void createdFromScratch_full_reload() throws Exception { r.addStep(new Statement() { @Override public void evaluate() throws Throwable { OrganizationFolder foo = r.j.createProject(OrganizationFolder.class, "foo"); foo.getSCMNavigators().add(new MockSCMNavigator(c, true, false, false)); foo.getProjectFactories() .replaceBy(Collections.singletonList(new BasicMultiBranchProjectFactory(null))); foo.scheduleBuild2(0).getFuture().get(); r.j.waitUntilNoActivity(); r.j.jenkins.reload(); assertDataMigrated(foo); } }); r.addStep(new Statement() { @Override public void evaluate() throws Throwable { TopLevelItem foo = r.j.jenkins.getItem("foo"); assertDataMigrated(foo); } }); } /** * Checks that data is migrated correctly from 1.x to current version. */ @Test @LocalData public void nameMangling_folder_reload() throws Exception { r.addStep(new Statement() { @Override public void evaluate() throws Throwable { OrganizationFolder foo = r.j.jenkins.getItemByFullName("foo", OrganizationFolder.class); foo.doReload(); assertDataMigrated(foo); } }); r.addStep(new Statement() { @Override public void evaluate() throws Throwable { TopLevelItem foo = r.j.jenkins.getItem("foo"); assertDataMigrated(foo); } }); } /** * Checks that data migrated from 1.x to 2.0.0 name mangling is still valid when re-migrated to 2.0.2 name mangling */ @Test @LocalData public void nameMangling_2_folder_reload() throws Exception { r.addStep(new Statement() { @Override public void evaluate() throws Throwable { OrganizationFolder foo = r.j.jenkins.getItemByFullName("foo", OrganizationFolder.class); foo.doReload(); assertDataMigrated(foo); } }); r.addStep(new Statement() { @Override public void evaluate() throws Throwable { TopLevelItem foo = r.j.jenkins.getItem("foo"); assertDataMigrated(foo); } }); } @Test public void createdFromScratch_folder_reload() throws Exception { r.addStep(new Statement() { @Override public void evaluate() throws Throwable { OrganizationFolder foo = r.j.createProject(OrganizationFolder.class, "foo"); foo.getSCMNavigators().add(new MockSCMNavigator(c, true, false, false)); foo.getProjectFactories() .replaceBy(Collections.singletonList(new BasicMultiBranchProjectFactory(null))); foo.scheduleBuild2(0).getFuture().get(); r.j.waitUntilNoActivity(); foo.doReload(); assertDataMigrated(foo); } }); r.addStep(new Statement() { @Override public void evaluate() throws Throwable { TopLevelItem foo = r.j.jenkins.getItem("foo"); assertDataMigrated(foo); } }); } private void assertDataMigrated(TopLevelItem foo) throws Exception { assertThat(foo, instanceOf(OrganizationFolder.class)); OrganizationFolder prj = (OrganizationFolder) foo; Map<String, MultiBranchProject> byName = new HashMap<>(); Map<String, MultiBranchProject> byDirName = new HashMap<>(); Map<String, MultiBranchProject> byDisplayName = new HashMap<>(); Map<String, Job> jobByName = new HashMap<>(); Map<String, Job> jobByDirName = new HashMap<>(); Map<String, Job> jobByDisplayName = new HashMap<>(); System.out.println("Jobs"); System.out.println("===="); System.out.println(); // Assume NFC String espana = "España"; String espanaMangled = "Espa_f1a.9jabqu"; String ireland = "Éireann"; String irelandMangled = "0_c9ireann.giuvlt"; String korea = "대한민국"; String koreaMangled = "0_b3_00_d5_5c_bb_fc_ad_6d.ufdgbs"; String korea2 = "특색/새로운"; String korea2Mangled = "0_d2_b9_c0_c.ps50ht._b8_5c_c6_b4"; for (MultiBranchProject<?, ?> p : prj.getItems()) { String dirName = p.getRootDir().getName(); System.out.printf("%s ==> %s ==> %s%n", dirName, p.getName(), p.getDisplayName()); byName.put(p.getName(), p); if (dirName.equals("Espan_03_03a.eqqe01")) { // NFD espana = "Espa\u006e\u0303a"; espanaMangled = "Espan_03_03a.eqqe01"; } if (dirName.equals("E_03_01ireann.0qtq11")) { // NFD ireland = "E\u0301ireann"; irelandMangled = "E_03_01ireann.0qtq11"; } if (dirName.equals("0_11_03_1.3gi5g2rs7pg4._6e_11_a8")) { // NFD korea = "\u1103\u1162\u1112\u1161\u11ab\u1106\u1175\u11ab\u1100\u116e\u11a8"; koreaMangled = "0_11_03_1.3gi5g2rs7pg4._6e_11_a8"; } byDirName.put(dirName, p); byDisplayName.put(p.getDisplayName(), p); for (Job<?, ?> j : p.getItems()) { String jobDirName = prj.getRootDir().getName() + "/" + p.getRootDir().getName() + "/" + j.getRootDir().getName(); System.out.printf(" %s ==> %s ==> %s%n", jobDirName, j.getName(), j.getDisplayName()); if (j.getName().equals("0_11_10_1.m479ph0h00p7._6e_11_ab")) { // NFD korea2 = "\u1110\u1173\u11a8\u1109\u1162\u11a8/\u1109\u1162\u1105\u1169\u110b\u116e\u11ab"; korea2Mangled = "0_11_10_1.m479ph0h00p7._6e_11_ab"; } jobByName.put(j.getFullName(), j); jobByDirName.put(jobDirName, j); jobByDisplayName.put(j.getFullDisplayName(), j); File nameFile = new File(j.getRootDir(), ChildNameGenerator.CHILD_NAME_FILE); assertThat("Exists: " + nameFile, nameFile.isFile(), is(true)); assertThat("Contents: " + nameFile, FileUtils.readFileToString(nameFile), is(j.getName())); } File nameFile = new File(p.getRootDir(), ChildNameGenerator.CHILD_NAME_FILE); assertThat("Exists: " + nameFile, nameFile.isFile(), is(true)); assertThat("Contents: " + nameFile, FileUtils.readFileToString(nameFile), is(p.getName())); } assertThat("Display Names are repo names", byDisplayName.keySet(), containsInAnyOrder( "test.example.com", ireland, "Россия", "中国", espana, korea )); assertThat("Directory names have been mangled", byDirName.keySet(), containsInAnyOrder( "test-example-com.34nhgh", irelandMangled, // Éireann "0_04_20_04_3.pei3d7._04_38_04_4f", // Россия "0_4e_2d_56_fd.m4k0dn", // 中国 espanaMangled, // España koreaMangled // 대한민국 )); assertThat("Folder names have been minimal url path segment pre-encoded", byName.keySet(), containsInAnyOrder( "test.example.com", ireland, // Éireann "Россия", // Россия "中国", // 中国 espana, // España korea // 대한민국 )); assertThat("Display Names are branch names", jobByDisplayName.keySet(), containsInAnyOrder( "foo » test.example.com » master", "foo » test.example.com » feature: browsing/part 1", "foo » test.example.com » feature: browsing/part 2", "foo » test.example.com » feature: welcome", "foo » " + ireland + " » master", "foo » " + ireland + " » gné/nua", "foo » Россия » master", "foo » Россия » особенность/новый", "foo » 中国 » master", "foo » 中国 » 特征/新", "foo » " + espana + " » master", "foo » " + espana + " » característica/nuevo", "foo » " + korea + " » master", "foo » " + korea + " » " + korea2 )); assertThat("Job directory names have been mangled", jobByDirName.keySet(), containsInAnyOrder( "foo/test-example-com.34nhgh/master", "foo/test-example-com.34nhgh/feature_3a-b.jk8rfi.wsing-part-1", "foo/test-example-com.34nhgh/feature_3a-b.m8lpav.wsing-part-2", "foo/test-example-com.34nhgh/feature_3a-welcome.9dhrtb", "foo/" + irelandMangled + "/master", "foo/" + irelandMangled + "/gn_e9-nua.updi5h", "foo/0_04_20_04_3.pei3d7._04_38_04_4f/master", "foo/0_04_20_04_3.pei3d7._04_38_04_4f/0_04_3e_0.n168ksdsksof._4b_04_39", "foo/0_4e_2d_56_fd.m4k0dn/master", "foo/0_4e_2d_56_fd.m4k0dn/0_72_79_5f_81-_65_b0.nt1m48", "foo/" + espanaMangled + "/master", "foo/" + espanaMangled + "/caracter_edstica-nuevo.h5da9f", "foo/" + koreaMangled + "/master", "foo/" + koreaMangled + "/" + korea2Mangled )); assertThat("Job names have been minimal url path segment pre-encoded", jobByName.keySet(), containsInAnyOrder( "foo/test.example.com/master", "foo/test.example.com/feature: browsing%2Fpart 1", "foo/test.example.com/feature: browsing%2Fpart 2", "foo/test.example.com/feature: welcome", "foo/" + ireland + "/master", "foo/" + ireland + "/gné%2Fnua", "foo/Россия/master", "foo/Россия/особенность%2Fновый", "foo/中国/master", "foo/中国/特征%2F新", "foo/" + espana + "/master", "foo/" + espana + "/característica%2Fnuevo", "foo/" + korea + "/master", "foo/" + korea + "/특색%2F새로운" )); assertThat(prj.getItemByProjectName(ireland), notNullValue()); assertThat(prj.getItemByProjectName(ireland).getItemByBranchName("gné/nua"), notNullValue()); } }