5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. 8 * 9 * This code is distributed in the hope that it will be useful, but WITHOUT 10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 12 * version 2 for more details (a copy is included in the LICENSE file that 13 * accompanied this code). 14 * 15 * You should have received a copy of the GNU General Public License version 16 * 2 along with this work; if not, write to the Free Software Foundation, 17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 20 * or visit www.oracle.com if you need additional information or have any 21 * questions. 22 */ 23 package jdk.jpackage.test; 24 25 import java.nio.file.Path; 26 import java.util.HashMap; 27 import java.util.Map; 28 import java.util.stream.Stream; 29 30 public class LinuxHelper { 31 private static String getRelease(JPackageCommand cmd) { 32 return cmd.getArgumentValue("--linux-app-release", () -> "1"); 33 } 34 35 public static String getPackageName(JPackageCommand cmd) { 36 cmd.verifyIsOfType(PackageType.LINUX); 37 return cmd.getArgumentValue("--linux-package-name", 38 () -> cmd.name().toLowerCase()); 39 } 40 41 static String getBundleName(JPackageCommand cmd) { 42 cmd.verifyIsOfType(PackageType.LINUX); 43 44 final PackageType packageType = cmd.packageType(); 45 String format = null; 46 switch (packageType) { 47 case LINUX_DEB: 51 case LINUX_RPM: 52 format = "%s-%s-%s.%s"; 53 break; 54 } 55 56 final String release = getRelease(cmd); 57 final String version = cmd.version(); 58 59 return String.format(format, 60 getPackageName(cmd), version, release, getPackageArch(packageType)) 61 + packageType.getSuffix(); 62 } 63 64 public static Stream<Path> getPackageFiles(JPackageCommand cmd) { 65 cmd.verifyIsOfType(PackageType.LINUX); 66 67 final PackageType packageType = cmd.packageType(); 68 final Path packageFile = cmd.outputBundle(); 69 70 Executor exec = new Executor(); 71 exec.saveOutput(); 72 switch (packageType) { 73 case LINUX_DEB: 74 exec.setExecutable("dpkg") 75 .addArgument("--contents") 76 .addArgument(packageFile); 77 break; 78 79 case LINUX_RPM: 80 exec.setExecutable("rpm") 81 .addArgument("-qpl") 82 .addArgument(packageFile); 83 break; 84 } 85 86 Stream<String> lines = exec.execute().assertExitCodeIsZero().getOutput().stream(); 87 if (packageType == PackageType.LINUX_DEB) { 88 // Typical text lines produced by dpkg look like: 89 // drwxr-xr-x root/root 0 2019-08-30 05:30 ./opt/appcategorytest/runtime/lib/ 90 // -rw-r--r-- root/root 574912 2019-08-30 05:30 ./opt/appcategorytest/runtime/lib/libmlib_image.so 91 // Need to skip all fields but absolute path to file. 92 lines = lines.map(line -> line.substring(line.indexOf(" ./") + 2)); 93 } 94 return lines.map(Path::of); 95 } 96 97 static Path getLauncherPath(JPackageCommand cmd) { 98 cmd.verifyIsOfType(PackageType.LINUX); 99 100 final String launcherName = cmd.name(); 101 final String launcherRelativePath = Path.of("/bin", launcherName).toString(); 102 103 return getPackageFiles(cmd).filter(path -> path.toString().endsWith( 104 launcherRelativePath)).findFirst().or(() -> { 105 Test.assertUnexpected(String.format( 106 "Failed to find %s in %s package", launcherName, 107 getPackageName(cmd))); 108 return null; 109 }).get(); 110 } 111 112 static String getDebBundleProperty(Path bundle, String fieldName) { 113 return new Executor() 114 .saveFirstLineOfOutput() 115 .setExecutable("dpkg-deb") 116 .addArguments("-f", bundle.toString(), fieldName) 117 .execute() 118 .assertExitCodeIsZero().getFirstLineOfOutput(); 119 } 120 121 static String geRpmBundleProperty(Path bundle, String fieldName) { 122 return new Executor() 123 .saveFirstLineOfOutput() 124 .setExecutable("rpm") 125 .addArguments( 126 "-qp", 127 "--queryformat", 128 String.format("%%{%s}", fieldName), 129 bundle.toString()) 130 .execute() 131 .assertExitCodeIsZero().getFirstLineOfOutput(); 132 } 133 134 private static String getPackageArch(PackageType type) { 135 if (archs == null) { 136 archs = new HashMap<>(); 137 } 138 139 String arch = archs.get(type); 140 if (arch == null) { 141 Executor exec = new Executor(); 142 exec.saveFirstLineOfOutput(); 143 switch (type) { 144 case LINUX_DEB: 145 exec.setExecutable("dpkg").addArgument( 146 "--print-architecture"); 147 break; 148 149 case LINUX_RPM: 150 exec.setExecutable("rpmbuild").addArgument( 151 "--eval=%{_target_cpu}"); 152 break; 153 } 154 arch = exec.execute().assertExitCodeIsZero().getFirstLineOfOutput(); 155 archs.put(type, arch); 156 } 157 return arch; 158 } 159 160 static private Map<PackageType, String> archs; 161 } | 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. 8 * 9 * This code is distributed in the hope that it will be useful, but WITHOUT 10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 12 * version 2 for more details (a copy is included in the LICENSE file that 13 * accompanied this code). 14 * 15 * You should have received a copy of the GNU General Public License version 16 * 2 along with this work; if not, write to the Free Software Foundation, 17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 20 * or visit www.oracle.com if you need additional information or have any 21 * questions. 22 */ 23 package jdk.jpackage.test; 24 25 import java.io.IOException; 26 import java.nio.file.Files; 27 import java.nio.file.Path; 28 import java.util.Arrays; 29 import java.util.HashMap; 30 import java.util.List; 31 import java.util.Map; 32 import java.util.Set; 33 import java.util.function.Function; 34 import java.util.stream.Stream; 35 36 public class LinuxHelper { 37 private static String getRelease(JPackageCommand cmd) { 38 return cmd.getArgumentValue("--linux-app-release", () -> "1"); 39 } 40 41 public static String getPackageName(JPackageCommand cmd) { 42 cmd.verifyIsOfType(PackageType.LINUX); 43 return cmd.getArgumentValue("--linux-package-name", 44 () -> cmd.name().toLowerCase()); 45 } 46 47 static String getBundleName(JPackageCommand cmd) { 48 cmd.verifyIsOfType(PackageType.LINUX); 49 50 final PackageType packageType = cmd.packageType(); 51 String format = null; 52 switch (packageType) { 53 case LINUX_DEB: 57 case LINUX_RPM: 58 format = "%s-%s-%s.%s"; 59 break; 60 } 61 62 final String release = getRelease(cmd); 63 final String version = cmd.version(); 64 65 return String.format(format, 66 getPackageName(cmd), version, release, getPackageArch(packageType)) 67 + packageType.getSuffix(); 68 } 69 70 public static Stream<Path> getPackageFiles(JPackageCommand cmd) { 71 cmd.verifyIsOfType(PackageType.LINUX); 72 73 final PackageType packageType = cmd.packageType(); 74 final Path packageFile = cmd.outputBundle(); 75 76 Executor exec = new Executor(); 77 switch (packageType) { 78 case LINUX_DEB: 79 exec.setExecutable("dpkg") 80 .addArgument("--contents") 81 .addArgument(packageFile); 82 break; 83 84 case LINUX_RPM: 85 exec.setExecutable("rpm") 86 .addArgument("-qpl") 87 .addArgument(packageFile); 88 break; 89 } 90 91 Stream<String> lines = exec.executeAndGetOutput().stream(); 92 if (packageType == PackageType.LINUX_DEB) { 93 // Typical text lines produced by dpkg look like: 94 // drwxr-xr-x root/root 0 2019-08-30 05:30 ./opt/appcategorytest/runtime/lib/ 95 // -rw-r--r-- root/root 574912 2019-08-30 05:30 ./opt/appcategorytest/runtime/lib/libmlib_image.so 96 // Need to skip all fields but absolute path to file. 97 lines = lines.map(line -> line.substring(line.indexOf(" ./") + 2)); 98 } 99 return lines.map(Path::of); 100 } 101 102 static Path getLauncherPath(JPackageCommand cmd) { 103 cmd.verifyIsOfType(PackageType.LINUX); 104 105 final String launcherName = cmd.name(); 106 final String launcherRelativePath = Path.of("/bin", launcherName).toString(); 107 108 return getPackageFiles(cmd).filter(path -> path.toString().endsWith( 109 launcherRelativePath)).findFirst().or(() -> { 110 Test.assertUnexpected(String.format( 111 "Failed to find %s in %s package", launcherName, 112 getPackageName(cmd))); 113 return null; 114 }).get(); 115 } 116 117 static long getInstalledPackageSizeKB(JPackageCommand cmd) { 118 cmd.verifyIsOfType(PackageType.LINUX); 119 120 final Path packageFile = cmd.outputBundle(); 121 switch (cmd.packageType()) { 122 case LINUX_DEB: 123 return Long.parseLong(getDebBundleProperty(packageFile, 124 "Installed-Size")); 125 126 case LINUX_RPM: 127 return Long.parseLong(getRpmBundleProperty(packageFile, "Size")) >> 10; 128 } 129 130 return 0; 131 } 132 133 static String getDebBundleProperty(Path bundle, String fieldName) { 134 return new Executor() 135 .setExecutable("dpkg-deb") 136 .addArguments("-f", bundle.toString(), fieldName) 137 .executeAndGetFirstLineOfOutput(); 138 } 139 140 static String getRpmBundleProperty(Path bundle, String fieldName) { 141 return new Executor() 142 .setExecutable("rpm") 143 .addArguments( 144 "-qp", 145 "--queryformat", 146 String.format("%%{%s}", fieldName), 147 bundle.toString()) 148 .executeAndGetFirstLineOfOutput(); 149 } 150 151 static void addDebBundleDesktopIntegrationVerifier(PackageTest test, 152 boolean integrated) { 153 Function<List<String>, String> verifier = (lines) -> { 154 // Lookup for xdg commands 155 return lines.stream().filter(line -> { 156 Set<String> words = Set.of(line.split("\\s+")); 157 return words.contains("xdg-desktop-menu") || words.contains( 158 "xdg-mime") || words.contains("xdg-icon-resource"); 159 }).findFirst().orElse(null); 160 }; 161 162 test.addBundleVerifier(cmd -> { 163 Test.withTempDirectory(tempDir -> { 164 try { 165 // Extract control Debian package files into temporary directory 166 new Executor() 167 .setExecutable("dpkg") 168 .addArguments( 169 "-e", 170 cmd.outputBundle().toString(), 171 tempDir.toString() 172 ).execute().assertExitCodeIsZero(); 173 174 Path controlFile = Path.of("postinst"); 175 176 // Lookup for xdg commands in postinstall script 177 String lineWithXsdCommand = verifier.apply( 178 Files.readAllLines(tempDir.resolve(controlFile))); 179 String assertMsg = String.format( 180 "Check if %s@%s control file uses xdg commands", 181 cmd.outputBundle(), controlFile); 182 if (integrated) { 183 Test.assertNotNull(lineWithXsdCommand, assertMsg); 184 } else { 185 Test.assertNull(lineWithXsdCommand, assertMsg); 186 } 187 } catch (IOException ex) { 188 throw new RuntimeException(ex); 189 } 190 }); 191 }); 192 } 193 194 static void initFileAssociationsTestFile(Path testFile) { 195 try { 196 // Write something in test file. 197 // On Ubuntu and Oracle Linux empty files are considered 198 // plain text. Seems like a system bug. 199 // 200 // $ >foo.jptest1 201 // $ xdg-mime query filetype foo.jptest1 202 // text/plain 203 // $ echo > foo.jptest1 204 // $ xdg-mime query filetype foo.jptest1 205 // application/x-jpackage-jptest1 206 // 207 Files.write(testFile, Arrays.asList("")); 208 } catch (IOException ex) { 209 throw new RuntimeException(ex); 210 } 211 } 212 213 private static Path getSystemDesktopFilesFolder() { 214 return Stream.of("/usr/share/applications", 215 "/usr/local/share/applications").map(Path::of).filter(dir -> { 216 return Files.exists(dir.resolve("defaults.list")); 217 }).findFirst().orElseThrow(() -> new RuntimeException( 218 "Failed to locate system .desktop files folder")); 219 } 220 221 static void addFileAssociationsVerifier(PackageTest test, FileAssociations fa) { 222 test.addInstallVerifier(cmd -> { 223 Test.withTempFile(fa.getSuffix(), testFile -> { 224 initFileAssociationsTestFile(testFile); 225 226 String mimeType = queryFileMimeType(testFile); 227 228 Test.assertEquals(fa.getMime(), mimeType, String.format( 229 "Check mime type of [%s] file", testFile)); 230 231 String desktopFileName = queryMimeTypeDefaultHandler(mimeType); 232 233 Path desktopFile = getSystemDesktopFilesFolder().resolve( 234 desktopFileName); 235 236 Test.assertFileExists(desktopFile, true); 237 238 Test.trace(String.format("Reading [%s] file...", desktopFile)); 239 String mimeHandler = null; 240 try { 241 mimeHandler = Files.readAllLines(desktopFile).stream().peek( 242 v -> Test.trace(v)).filter( 243 v -> v.startsWith("Exec=")).map( 244 v -> v.split("=", 2)[1]).findFirst().orElseThrow(); 245 } catch (IOException ex) { 246 throw new RuntimeException(ex); 247 } 248 Test.trace(String.format("Done")); 249 250 Test.assertEquals(cmd.launcherInstallationPath().toString(), 251 mimeHandler, String.format( 252 "Check mime type handler is the main application launcher")); 253 254 }); 255 }); 256 257 test.addUninstallVerifier(cmd -> { 258 Test.withTempFile(fa.getSuffix(), testFile -> { 259 initFileAssociationsTestFile(testFile); 260 261 String mimeType = queryFileMimeType(testFile); 262 263 Test.assertNotEquals(fa.getMime(), mimeType, String.format( 264 "Check mime type of [%s] file", testFile)); 265 266 String desktopFileName = queryMimeTypeDefaultHandler(fa.getMime()); 267 268 Test.assertNull(desktopFileName, String.format( 269 "Check there is no default handler for [%s] mime type", 270 fa.getMime())); 271 }); 272 }); 273 } 274 275 private static String queryFileMimeType(Path file) { 276 return new Executor() 277 .setExecutable("xdg-mime") 278 .addArguments("query", "filetype", file.toString()) 279 .executeAndGetFirstLineOfOutput(); 280 } 281 282 private static String queryMimeTypeDefaultHandler(String mimeType) { 283 return new Executor() 284 .setExecutable("xdg-mime") 285 .addArguments("query", "default", mimeType) 286 .executeAndGetFirstLineOfOutput(); 287 } 288 289 private static String getPackageArch(PackageType type) { 290 if (archs == null) { 291 archs = new HashMap<>(); 292 } 293 294 String arch = archs.get(type); 295 if (arch == null) { 296 Executor exec = new Executor(); 297 switch (type) { 298 case LINUX_DEB: 299 exec.setExecutable("dpkg").addArgument( 300 "--print-architecture"); 301 break; 302 303 case LINUX_RPM: 304 exec.setExecutable("rpmbuild").addArgument( 305 "--eval=%{_target_cpu}"); 306 break; 307 } 308 arch = exec.executeAndGetFirstLineOfOutput(); 309 archs.put(type, arch); 310 } 311 return arch; 312 } 313 314 static private Map<PackageType, String> archs; 315 } |