1 /* 2 * CDDL HEADER START 3 * 4 * The contents of this file are subject to the terms of the 5 * Common Development and Distribution License (the "License"). 6 * You may not use this file except in compliance with the License. 7 * 8 * See LICENSE.txt included in this distribution for the specific 9 * language governing permissions and limitations under the License. 10 * 11 * When distributing Covered Code, include this CDDL HEADER in each 12 * file and include the License file at LICENSE.txt. 13 * If applicable, add the following below this CDDL HEADER, with the 14 * fields enclosed by brackets "[]" replaced with your own identifying 15 * information: Portions Copyright [yyyy] [name of copyright owner] 16 * 17 * CDDL HEADER END 18 */ 19 20 /* 21 * Copyright (c) 2008, 2022, Oracle and/or its affiliates. All rights reserved. 22 * Portions Copyright (c) 2018, 2019, Chris Fraire <cfraire@me.com>. 23 */ 24 package org.opengrok.indexer.util; 25 26 import java.io.File; 27 import java.io.FileOutputStream; 28 import java.io.IOException; 29 import java.io.InputStream; 30 import java.net.URISyntaxException; 31 import java.net.URL; 32 import java.nio.file.FileSystems; 33 import java.nio.file.Files; 34 import java.nio.file.Path; 35 import java.util.LinkedHashMap; 36 import java.util.Map; 37 import java.util.stream.Stream; 38 39 import org.jetbrains.annotations.NotNull; 40 import org.opengrok.indexer.configuration.RuntimeEnvironment; 41 42 import static java.nio.file.StandardCopyOption.COPY_ATTRIBUTES; 43 import static java.nio.file.StandardCopyOption.REPLACE_EXISTING; 44 import static org.junit.jupiter.api.Assertions.assertFalse; 45 import static org.junit.jupiter.api.Assertions.assertNotNull; 46 import static org.junit.jupiter.api.Assertions.assertTrue; 47 48 /** 49 * A source repository to be used during a test. 50 * 51 * @author Trond Norbye 52 */ 53 public class TestRepository { 54 55 private static final String URL_FILE_PROTOCOL = "file"; 56 private static final char JAR_PATH_DELIMITER = '!'; 57 58 private static final Map<Path, Path> renameMappings = new LinkedHashMap<>(Map.of( 59 Path.of("bazaar", "bzr"), 60 Path.of("bazaar", ".bzr"), 61 62 Path.of("bitkeeper", "bk"), 63 Path.of("bitkeeper", ".bk"), 64 65 Path.of("mercurial", "hg"), 66 Path.of("mercurial", ".hg"), 67 68 Path.of("mercurial", "hgignore"), 69 Path.of("mercurial", ".hgignore"), 70 71 Path.of("git", "git"), 72 Path.of("git", ".git"), 73 74 Path.of("cvs_test", "cvsrepo", "CVS_dir"), 75 Path.of("cvs_test", "cvsrepo", "CVS") 76 )); 77 78 private final RuntimeEnvironment env; 79 private File sourceRoot; 80 private File dataRoot; 81 private File externalRoot; 82 TestRepository()83 public TestRepository() { 84 env = RuntimeEnvironment.getInstance(); 85 } 86 createEmpty()87 public void createEmpty() throws IOException { 88 sourceRoot = Files.createTempDirectory("source").toFile(); 89 dataRoot = Files.createTempDirectory("data").toFile(); 90 env.setSourceRoot(sourceRoot.getAbsolutePath()); 91 env.setDataRoot(dataRoot.getAbsolutePath()); 92 } 93 create(@otNull final URL url)94 public void create(@NotNull final URL url) throws IOException, URISyntaxException { 95 createEmpty(); 96 if (url.getProtocol().equals(URL_FILE_PROTOCOL)) { 97 copyDirectory(Path.of(url.toURI()), sourceRoot.toPath()); 98 } else { 99 try (var fs = FileSystems.newFileSystem(url.toURI(), Map.of())) { 100 var urlStr = url.toString(); 101 copyDirectory(fs.getPath(urlStr.substring(urlStr.indexOf(JAR_PATH_DELIMITER) + 1)), 102 sourceRoot.toPath()); 103 } 104 } 105 } 106 107 /** 108 * Assumes the destination directory exists. 109 * @param src source directory 110 * @param dest destination directory 111 * @throws IOException on error 112 */ copyDirectory(Path src, Path dest)113 public void copyDirectory(Path src, Path dest) throws IOException { 114 try (Stream<Path> stream = Files.walk(src)) { 115 stream.forEach(sourceFile -> { 116 if (sourceFile.equals(src)) { 117 return; 118 } 119 try { 120 Path destRelativePath = getDestinationRelativePath(src, sourceFile); 121 Path destPath = dest.resolve(destRelativePath); 122 if (Files.isDirectory(sourceFile)) { 123 if (!Files.exists(destPath)) { 124 Files.createDirectory(destPath); 125 } 126 return; 127 } 128 Files.copy(sourceFile, destPath, REPLACE_EXISTING, COPY_ATTRIBUTES); 129 } catch (Exception e) { 130 throw new RuntimeException(e); 131 } 132 }); 133 } 134 } 135 getDestinationRelativePath(Path sourceDirectory, Path sourceFile)136 private Path getDestinationRelativePath(Path sourceDirectory, Path sourceFile) { 137 // possibly strip zip filesystem for the startsWith method to work 138 var relativePath = Path.of(sourceDirectory.relativize(sourceFile).toString()); 139 for (var e : renameMappings.entrySet()) { 140 if (relativePath.startsWith(e.getKey())) { 141 if (relativePath.getNameCount() > e.getKey().getNameCount()) { 142 relativePath = relativePath.subpath(e.getKey().getNameCount(), relativePath.getNameCount()); 143 } else { 144 relativePath = Path.of(""); 145 } 146 relativePath = e.getValue().resolve(relativePath); 147 break; 148 } 149 } 150 return relativePath; 151 } 152 create(InputStream inputBundle)153 public void create(InputStream inputBundle) throws IOException { 154 createEmpty(); 155 extractBundle(sourceRoot, inputBundle); 156 } 157 createExternal(InputStream inputBundle)158 public void createExternal(InputStream inputBundle) throws IOException { 159 createEmpty(); 160 externalRoot = Files.createTempDirectory("external").toFile(); 161 extractBundle(externalRoot, inputBundle); 162 } 163 destroy()164 public void destroy() { 165 try { 166 if (sourceRoot != null) { 167 IOUtils.removeRecursive(sourceRoot.toPath()); 168 } 169 if (externalRoot != null) { 170 IOUtils.removeRecursive(externalRoot.toPath()); 171 } 172 if (dataRoot != null) { 173 IOUtils.removeRecursive(dataRoot.toPath()); 174 } 175 } catch (IOException ignore) { 176 // ignored 177 } 178 } 179 180 /** 181 * Deletes the directory tree of {@link #getDataRoot()}, and then recreates 182 * the empty directory afterward. 183 */ purgeData()184 public void purgeData() throws IOException { 185 if (dataRoot != null) { 186 IOUtils.removeRecursive(dataRoot.toPath()); 187 assertFalse(dataRoot.exists(), "dataRoot should not exist"); 188 assertTrue(dataRoot.mkdir(), "should recreate dataRoot"); 189 } 190 } 191 getSourceRoot()192 public String getSourceRoot() { 193 return sourceRoot.getAbsolutePath(); 194 } 195 getDataRoot()196 public String getDataRoot() { 197 return dataRoot.getAbsolutePath(); 198 } 199 getExternalRoot()200 public String getExternalRoot() { 201 return externalRoot == null ? null : externalRoot.getAbsolutePath(); 202 } 203 204 private static final String DUMMY_FILENAME = "dummy.txt"; 205 addDummyFile(String project)206 public File addDummyFile(String project) throws IOException { 207 File dummy = new File(getSourceRoot() + File.separator + project + 208 File.separator + DUMMY_FILENAME); 209 if (!dummy.exists()) { 210 dummy.createNewFile(); 211 } 212 return dummy; 213 } 214 addDummyFile(String project, String contents)215 public void addDummyFile(String project, String contents) throws IOException { 216 File dummy = addDummyFile(project); 217 Files.write(dummy.toPath(), contents.getBytes()); 218 } 219 removeDummyFile(String project)220 public void removeDummyFile(String project) { 221 File dummy = new File(getSourceRoot() + File.separator + project + 222 File.separator + DUMMY_FILENAME); 223 dummy.delete(); 224 } 225 226 /** 227 * Add an ad-hoc file of a specified name with contents from the specified 228 * stream. 229 * @param filename a required instance 230 * @param in a required instance 231 * @param project an optional project name 232 * @return file object 233 * @throws IOException I/O exception 234 */ addAdhocFile(String filename, InputStream in, String project)235 public File addAdhocFile(String filename, InputStream in, String project) 236 throws IOException { 237 238 String projsep = project != null ? File.separator + project : ""; 239 File adhoc = new File(getSourceRoot() + projsep + File.separator + 240 filename); 241 242 byte[] buf = new byte[8192]; 243 try (FileOutputStream out = new FileOutputStream(adhoc)) { 244 int r; 245 if ((r = in.read(buf)) != -1) { 246 out.write(buf, 0, r); 247 } 248 } 249 return adhoc; 250 } 251 extractBundle(File target, InputStream inputBundle)252 private void extractBundle(File target, InputStream inputBundle) throws IOException { 253 File sourceBundle = null; 254 try { 255 sourceBundle = File.createTempFile("srcbundle", ".zip"); 256 if (sourceBundle.exists()) { 257 assertTrue(sourceBundle.delete()); 258 } 259 260 assertNotNull(inputBundle, "inputBundle should not be null"); 261 FileOutputStream out = new FileOutputStream(sourceBundle); 262 FileUtilities.copyFile(inputBundle, out); 263 out.close(); 264 FileUtilities.extractArchive(sourceBundle, target); 265 } finally { 266 if (sourceBundle != null) { 267 sourceBundle.delete(); 268 } 269 } 270 } 271 } 272