Compare commits

..

7 commits

Author SHA1 Message Date
Mikolaj Izdebski
ebaca6ff06 Still allow /usr/lib/java/javapackages-bootstrap 2024-07-31 14:54:53 +02:00
Mikolaj Izdebski
9739fc20a2 Allow /usr/lib/javapackages-bootstrap for javapackages-bootstrap 2024-07-31 14:54:51 +02:00
Mikolaj Izdebski
43c22349f3 Implement jpv_flavor fmf dimension
Vanilla flavor runs unmodified upstream validator, skipping extra
validators from src/ directory.
2024-07-17 20:16:00 +02:00
Mikolaj Izdebski
a82344b624 Disable built-in validator factory
Disable the use of upstream DefaultValidatorFactory, so that we have
more control of which validators are executed.
2024-07-11 16:42:12 +02:00
Marian Koncek
ed326c9eb1 Put javapackages test code into a separate shell script 2024-07-10 11:43:00 +02:00
Marian Koncek
6cb24ea1a1 Unify indentation to use 2 spaces 2024-07-09 15:30:53 +02:00
Marian Koncek
efe08cfac1 Add more javapackages test metadata 2024-07-09 15:29:56 +02:00
22 changed files with 157 additions and 576 deletions

View file

@ -1,5 +1,23 @@
# Deprecated
# javapackages-validator-tests
This repository is deprecated.
This repository contains the `.fmf` test files referenced by packages that are tested by _javapackages-validator_ tool.
Javapackages tests were moved to https://gitlab.com/redhat/centos-stream/tests/javapackages
## Versioning
The version scheme is based on Fedora versions (`f<NUM>`).
Changes made to a single branch must be backwards compatible or fix a bug.
Breaking changes can be made to a branch that corresponds to the Rawhide branch at the time of submission.
Packages then explicitly update their git reference and new versions of the package will be tested with the new tests.
This approach ensures that changes to old versions of packages will not be tested with possibly incompatible tests.
## Forking
If you wish to fork this project and add your own tests, you should follow these steps:
1. Add a source directory (e.g. `src`) with your own validators.
2. Add a text [service file](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/ServiceLoader.html) named `org.fedoraproject.javapackages.validator.Validator` into the same directory.
This file contains fully-qualified class names of validators which you want to use, one name per line.
Javapackages-validator will run all the validators listed in the service file, unless they have been explicitly filtered using per-package configuration.
This is explained in the project documentation.

View file

@ -1,7 +1,6 @@
summary: Javapackages-validator tests
contact:
- Marian Koncek <mkoncek@redhat.com>
- Mikolaj Izdebski <mizdebsk@redhat.com>
description: |
This test executes a series of tests checking for various aspects of the Java package.
This includes packaging guidelines compliance, package sanity and various regressions.
@ -15,17 +14,13 @@ environment:
JP_VALIDATOR_IMAGE: quay.io/fedora-java/javapackages-validator:2
JP_VALIDATOR_SOURCE_PATH: src
JP_VALIDATOR_OUTPUT_DIR: ""
JP_VALIDATOR_ARGS: |
org.fedoraproject.javapackages.validator.validators.jp.ValidatorFactoryGeneric
org.fedoraproject.javapackages.validator.validators.jp.ValidatorFactoryJP
io.kojan.runit.validator.RunitValidatorFactory
JP_VALIDATOR_ARGS: org.fedoraproject.javapackages.validator.validators.jp.ValidatorFactoryJP
result: custom
test: exec ./javapackages.sh
/javapackages-validator:
adjust:
# Generic flavor. Run selected upstream validators as well certain downstream ones.
- when: jpv_flavor == generic
environment+:
JP_VALIDATOR_ARGS: |
org.fedoraproject.javapackages.validator.validators.jp.ValidatorFactoryGeneric
when: jpv_flavor==vanilla
environment+:
JP_VALIDATOR_SOURCE_PATH: ""
JP_VALIDATOR_ARGS: ""

14
runit.fmf Normal file
View file

@ -0,0 +1,14 @@
summary: Runit tests
description: |
Run unit tests written in Java that are stored in runit/ subdirectory.
contact: Mikolaj Izdebski <mizdebsk@redhat.com>
tier: 0
tag: runit
environment+:
JP_VALIDATOR_IMAGE: quay.io/mizdebsk/runit:latest
JP_VALIDATOR_ARGS: io.kojan.runit.validator.RunitValidatorFactory
JP_VALIDATOR_SOURCE_PATH: runit
enabled: false
adjust:
enabled: true
when: runit==true

View file

@ -0,0 +1,51 @@
package tests;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.hasItem;
import static org.hamcrest.Matchers.*;
import static org.junit.jupiter.api.Assumptions.assumeFalse;
import static org.junit.jupiter.api.Assumptions.assumeTrue;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.DisplayName;
import io.kojan.javadeptools.rpm.RpmDependency;
import io.kojan.javadeptools.rpm.RpmInfo;
import io.kojan.runit.api.PackageTest;
@DisplayName("/apache-commons")
public class ApacheCommonsCheck {
private void assumeApacheCommonsPackage(RpmInfo rpm) {
assumeTrue(startsWith("apache-commons-").matches(rpm.getName()),
"This test is valid only for Apache Commons packages");
}
@PackageTest
public void saneName(RpmInfo rpm) {
assumeApacheCommonsPackage(rpm);
assertThat("Package name is sane", rpm.getName(),
either(matchesRegex("apache-commons-[a-z][a-z0-9]+(-javadoc)?"))
.or(is("apache-commons-collections-testframework")));
}
@PackageTest
@Disabled("JPMS provides are not generated for now")
public void jpmsProvides(RpmInfo rpm) {
assumeApacheCommonsPackage(rpm);
assumeFalse(rpm.isSourcePackage(), "Skip source packages");
assumeFalse(is("apache-commons-parent").matches(rpm.getName()), "Skip apache-commons-parent");
Pattern pattern = Pattern.compile("apache-commons-([a-z][a-z0-9]+)");
Matcher matcher = pattern.matcher(rpm.getName());
assumeTrue(matcher.matches(), "Skip javadoc and other binary subpackages");
String id = matcher.group(1);
List<String> prov = rpm.getProvides().stream().map(RpmDependency::getName).collect(Collectors.toList());
assertThat("Apache Commons packages should have jpms provides", prov,
hasItem("jpms(org.apache.commons." + id + ")"));
}
}

36
runit/BasicCheck.java Normal file
View file

@ -0,0 +1,36 @@
package tests;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.endsWith;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.matchesRegex;
import static org.hamcrest.Matchers.not;
import static org.junit.jupiter.api.Assumptions.assumeFalse;
import static org.junit.jupiter.api.Assumptions.assumeTrue;
import org.junit.jupiter.api.DisplayName;
import io.kojan.javadeptools.rpm.RpmInfo;
import io.kojan.runit.api.PackageTest;
@DisplayName("/basic")
public class BasicCheck {
@PackageTest
public void noBootstrapRelease(RpmInfo rpm) {
assertThat("No ~bootstrap in release", rpm.getRelease(), not(endsWith("~bootstrap")));
}
@PackageTest
public void saneName(RpmInfo rpm) {
assumeFalse(rpm.getName().startsWith("java_cup"));
assertThat("Package name is sane", rpm.getName(), matchesRegex("[a-z][a-z0-9-]+[a-z0-9]"));
}
@PackageTest
public void testJavadocNoarch(RpmInfo rpm) {
assumeFalse(rpm.isSourcePackage());
assumeTrue(rpm.getName().endsWith("-javadoc"));
assertThat("Javadoc package is noarch", rpm.getArch(), is("noarch"));
}
}

View file

@ -66,7 +66,7 @@ public class BytecodeVersionJarValidatorJP extends BytecodeVersionJarValidator {
for (var entry : classVersions.entrySet()) {
Path classPath = entry.getKey();
int version = entry.getValue().major();
int maxAllowedVersion = 21;
int maxAllowedVersion = 17;
int expected;
if (classPath.endsWith("module-info.class") || classPath.endsWith("package-info.class")) {

View file

@ -16,13 +16,16 @@ public class DuplicateFileValidatorJP extends DefaultDuplicateFileValidator {
@Override
public boolean allowedDuplicateFile(Path path, Collection<? extends RpmInfo> providerRpms) throws Exception {
if (path.getParent().equals(Path.of("/usr/share/jpbinding"))) {
if (path.toString().startsWith("/usr/share/licenses/")) {
return providerRpms.stream().map(Common::getPackageName).distinct().count() == 1;
}
if (providerRpms.stream().allMatch(rpm -> rpm.getName().startsWith("maven-openjdk"))) {
return true;
}
// FIXME we want to get rid of these duplicates
if (path.toString().startsWith("/usr/share/licenses/")) {
return providerRpms.stream().map(Common::getPackageName).distinct().count() == 1;
if (providerRpms.stream().allMatch(rpm -> rpm.getName().startsWith("maven-local-openjdk"))) {
return true;
}
return false;

View file

@ -2,20 +2,16 @@ package org.fedoraproject.javapackages.validator.validators.jp;
import java.nio.file.Path;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.fedoraproject.javapackages.validator.spi.Decorated;
import org.fedoraproject.javapackages.validator.util.Common;
import org.fedoraproject.javapackages.validator.util.FilesValidator;
import io.kojan.javadeptools.rpm.RpmInfo;
import io.kojan.javadeptools.rpm.RpmPackage;
public class FilesValidatorJP extends FilesValidator {
@Override
@ -33,19 +29,11 @@ public class FilesValidatorJP extends FilesValidator {
}
private static class Prefix {
static final String JPBINDING = "/usr/share/jpbinding";
static final String ETC_PKG = "/etc/${package}";
static final String ETC_JAVA = "/etc/java";
static final String ETC = "/etc/${package}";
static final String USR_BIN = "/usr/bin/";
static final String USR_LIB_BUILD_ID = "/usr/lib/.build-id";
}
private static Set<String> ignoredComponents = new LinkedHashSet<>();
static {
ignoredComponents.add("javapackages-bootstrap");
ignoredComponents.add("javapackages-tools");
}
private static Map<String, List<String>> nameAliases = new TreeMap<>();
static {
nameAliases.put("maven-resolver", List.of("aether"));
@ -60,33 +48,31 @@ public class FilesValidatorJP extends FilesValidator {
private static Map<String, List<String>> prefixes = new TreeMap<>();
static {
prefixes.put("ant", List.of(Prefix.ETC_PKG, Prefix.ETC_JAVA, Prefix.JPBINDING, Prefix.USR_BIN));
prefixes.put("ant", List.of(Prefix.ETC, Prefix.USR_BIN));
prefixes.put("antlr", List.of(Prefix.USR_BIN));
prefixes.put("aqute-bnd", List.of(Prefix.USR_BIN));
prefixes.put("byaccj", List.of(Prefix.USR_BIN, Prefix.USR_LIB_BUILD_ID));
prefixes.put("jansi", List.of(Prefix.USR_LIB_BUILD_ID));
prefixes.put("javacc", List.of(Prefix.USR_BIN));
prefixes.put("java_cup", List.of(Prefix.USR_BIN));
prefixes.put("javapackages-bootstrap", List.of("/usr/lib/java/javapackages-bootstrap", "/usr/lib/javapackages-bootstrap", "/usr/lib/rpm", "/etc/rpm", "/usr/libexec/javapackages-bootstrap"));
prefixes.put("javapackages-tools", List.of(Prefix.USR_BIN, "/usr/share/xmvn/conf", "/usr/lib/rpm", "/usr/share/java-utils/", "/usr/lib/eclipse", "/usr/share/eclipse", "/etc/java", "/etc/jvm", "/usr/lib/java", "/usr/lib/jvm", "/etc/ivy", "/etc/ant.d"));
prefixes.put("jflex", List.of(Prefix.USR_BIN));
prefixes.put("jline", List.of(Prefix.USR_LIB_BUILD_ID));
prefixes.put("junit5", List.of(Prefix.USR_BIN));
prefixes.put("maven", List.of(Prefix.ETC_PKG, Prefix.ETC_JAVA, Prefix.JPBINDING, Prefix.USR_BIN));
prefixes.put("maven4", List.of(Prefix.ETC_PKG, Prefix.ETC_JAVA, Prefix.JPBINDING, Prefix.USR_BIN));
prefixes.put("maven", List.of(Prefix.ETC));
prefixes.put("modello", List.of(Prefix.USR_BIN));
prefixes.put("objectweb-asm", List.of(Prefix.USR_BIN));
prefixes.put("xerces-j2", List.of(Prefix.USR_BIN));
prefixes.put("xml-commons-resolver", List.of(Prefix.USR_BIN));
prefixes.put("xmvn", List.of(Prefix.JPBINDING, Prefix.USR_BIN));
prefixes.put("xmvn", List.of(Prefix.USR_BIN));
prefixes.put("xmvn-generator", List.of("/usr/lib/rpm", "/etc/rpm"));
}
private static Map<String, List<String>> exceptionalFiles = new TreeMap<>();
static {
exceptionalFiles.put("aqute-bnd", List.of("/etc/ant.d/aqute-bnd"));
exceptionalFiles.put("maven", List.of("/etc/m2.conf"));
exceptionalFiles.put("maven4", List.of("/etc/m24.conf"));
exceptionalFiles.put("maven", List.of("/etc/m2.conf", "/etc/java/maven.conf"));
exceptionalFiles.put("jansi", List.of("/usr/lib/jansi", "/usr/lib/jansi/libjansi.so", "/usr/lib/java/jansi"));
exceptionalFiles.put("jline", List.of("/usr/lib/jline", "/usr/lib/jline/libjlinenative.so", "/usr/lib/java/jline"));
exceptionalFiles.put("javapackages-tools", List.of("/usr/share/java-utils", "/usr/share/xmvn", "/usr/share/ivy-xmls", "/usr/share/java", "/usr/share/javadoc", "/usr/share/jvm", "/usr/share/jvm-common", "/usr/share/maven-metadata", "/usr/share/maven-poms"));
}
private static boolean namesRelated(String lhs, String rhs) {
@ -169,7 +155,7 @@ public class FilesValidatorJP extends FilesValidator {
}
if (patternsCache.computeIfAbsent(Common.getPackageName(rpm) + "/javadoc",
_ -> Pattern.compile("/usr/share/javadoc/" + Common.getPackageName(rpm) + "(:?/.*)?"))
p -> Pattern.compile("/usr/share/javadoc/" + Common.getPackageName(rpm) + "(:?/.*)?"))
.matcher(filename).matches()) {
return true;
}
@ -331,16 +317,6 @@ public class FilesValidatorJP extends FilesValidator {
return false;
}
@Override
public void validate(RpmPackage rpm) throws Exception {
if (ignoredComponents.contains(rpm.getInfo().getSourceName())) {
skip("{0}: the component {1} is ignored", Decorated.rpm(rpm),
Decorated.actual(rpm.getInfo().getSourceName()));
} else {
super.validate(rpm);
}
}
@Override
public boolean allowedFile(RpmInfo rpm, Path path) {
if (rpm.isSourcePackage()) {
@ -348,6 +324,11 @@ public class FilesValidatorJP extends FilesValidator {
return true;
}
// if (Common.getPackageName(rpm).equals("javapackages-bootstrap")) {
// // TODO
// return true;
// }
String filename = path.toString();
if (rpm.getName().equals(Common.getPackageName(rpm) + "-javadoc") || rpm.getName().equals(Common.getPackageName(rpm) + "-javadocs")) {

View file

@ -1,27 +0,0 @@
package org.fedoraproject.javapackages.validator.validators.jp;
import java.util.Set;
import org.fedoraproject.javapackages.validator.spi.Decorated;
import org.fedoraproject.javapackages.validator.validators.JavaExclusiveArchValidator;
import io.kojan.javadeptools.rpm.RpmPackage;
public class JavaExclusiveArchValidatorJP extends JavaExclusiveArchValidator {
@Override
public String getTestName() {
return "/java/jp/exclusive_arch";
}
private static final Set<String> SKIPPED = Set.of("javapackages-tools", "byaccj", "jurand");
@Override
public void validate(RpmPackage rpm) throws Exception {
String component = rpm.getInfo().getSourceName();
if (SKIPPED.contains(component)) {
skip("Skipping rpm {0}", Decorated.rpm(rpm));
return;
}
super.validate(rpm);
}
}

View file

@ -1,23 +0,0 @@
package org.fedoraproject.javapackages.validator.validators.jp;
import org.apache.commons.compress.archivers.cpio.CpioArchiveEntry;
import org.fedoraproject.javapackages.validator.spi.Decorated;
import org.fedoraproject.javapackages.validator.validators.JpmsProvidesValidator;
import io.kojan.javadeptools.rpm.RpmPackage;
public class JpmsProvidesValidatorJP extends JpmsProvidesValidator {
@Override
public String getTestName() {
return "/java/jp/jpms-provides";
}
@Override
public void acceptJarEntry(RpmPackage rpm, CpioArchiveEntry cpio, byte[] bytes) throws Exception {
if (rpm.getInfo().getSourceName().equals("javapackages-bootstrap")) {
skip("Skipping rpm {0}", Decorated.rpm(rpm));
return;
}
super.acceptJarEntry(rpm, cpio, bytes);
}
}

View file

@ -18,7 +18,7 @@ public class RpmFilesizeValidatorJP extends RpmFilesizeValidator {
}
if (Common.getPackageName(rpm).equals("javapackages-bootstrap")) {
return sizeBytes <= 60_000_000;
return sizeBytes <= 40_000_000;
}
// javadoc rpms
@ -33,6 +33,6 @@ public class RpmFilesizeValidatorJP extends RpmFilesizeValidator {
}
}
return sizeBytes <= 4_000_000;
return sizeBytes <= 3_500_000;
}
}

View file

@ -1,20 +0,0 @@
package org.fedoraproject.javapackages.validator.validators.jp;
import java.util.List;
import org.fedoraproject.javapackages.validator.spi.Validator;
import org.fedoraproject.javapackages.validator.spi.ValidatorFactory;
import org.fedoraproject.javapackages.validator.validators.*;
public class ValidatorFactoryGeneric implements ValidatorFactory {
@Override
public List<Validator> getValidators() {
return List.of(new Validator[] {
new AttributeProvidesValidator(),
new JavadocNoarchValidator(),
new JavaExclusiveArchValidatorJP(),
new MavenMetadataValidator(),
new NoBootstrapValidator(),
});
}
}

View file

@ -4,11 +4,17 @@ import java.util.List;
import org.fedoraproject.javapackages.validator.spi.Validator;
import org.fedoraproject.javapackages.validator.spi.ValidatorFactory;
import org.fedoraproject.javapackages.validator.validators.*;
public class ValidatorFactoryJP implements ValidatorFactory {
@Override
public List<Validator> getValidators() {
return List.of(new Validator[] {
new AttributeProvidesValidator(),
new JavadocNoarchValidator(),
new JavaExclusiveArchValidator(),
new MavenMetadataValidator(),
new NoBootstrapValidator(),
new BytecodeVersionJarValidatorJP(),
new DuplicateFileValidatorJP(),
new FilesValidatorJP(),

View file

@ -1,13 +0,0 @@
compiler.release=22
#repositories=https://s01.oss.sonatype.org/content/repositories/snapshots
dependencies=\
io.kojan:runit-api:1.0.0 \
io.kojan:runit-validator:1.0.0 \
org.apiguardian:apiguardian-api:1.1.2 \
org.hamcrest:hamcrest:3.0 \
org.junit.jupiter:junit-jupiter-api:5.11.3 \
org.junit.jupiter:junit-jupiter-engine:5.11.3 \
org.junit.platform:junit-platform-commons:1.11.3 \
org.junit.platform:junit-platform-engine:1.11.3 \
org.junit.platform:junit-platform-launcher:1.11.3 \
org.opentest4j:opentest4j:1.3.0 \

View file

@ -1,2 +1 @@
org.fedoraproject.javapackages.validator.validators.jp.ValidatorFactoryGeneric
org.fedoraproject.javapackages.validator.validators.jp.ValidatorFactoryJP

View file

@ -1,37 +0,0 @@
package tests;
import static io.kojan.runit.api.RUnit.*;
import static io.kojan.runit.api.matcher.RUnitMatchers.*;
import static org.hamcrest.Matchers.*;
import org.junit.jupiter.api.DisplayName;
import io.kojan.javadeptools.rpm.*;
import io.kojan.runit.api.*;
@DisplayName("/apache-commons")
public class ApacheCommonsCheck {
@PackageTest
@IncludeSource("apache-commons-.*")
@IncludeBinary("apache-commons-.*")
@ExcludeBinary("apache-commons-collections-testframework")
public void saneName(RpmInfo rpm) {
assertThat("Package name is sane", rpm.getName(), matchesRegex("apache-commons-[a-z][a-z0-9]+(-javadoc)?"));
}
@PackageTest
@IncludeBinary("apache-commons-[a-z][a-z0-9]+")
@ExcludeBinary("apache-commons-parent") // Parent POM, no Java code
public void jpmsProvides(RpmInfo rpm) {
String id = rpm.getName().substring("apache-commons-".length());
assertThat("Apache Commons packages should have jpms provides", rpm,
provides("jpms\\(org.apache.commons." + id + "\\)( = .*)?"));
}
@PackageTest
@IncludeBinary("apache-commons-.*")
public void apacheLicense(RpmInfo rpm) {
assertThat("Apache Commons packages license should be Apache-2.0", rpm.getLicense(), is("Apache-2.0"));
}
}

View file

@ -1,57 +0,0 @@
package tests;
import static io.kojan.runit.api.RUnit.*;
import static io.kojan.runit.api.matcher.RUnitMatchers.*;
import static org.hamcrest.Matchers.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.junit.jupiter.api.DisplayName;
import io.kojan.javadeptools.rpm.*;
import io.kojan.runit.api.*;
@DisplayName("/basic")
public class BasicCheck {
@PackageTest
public void noBootstrapRelease(RpmInfo rpm) {
assertThat("No ~bootstrap in release", rpm.getRelease(), not(endsWith("~bootstrap")));
}
@PackageTest
@ExcludeSource("java_cup")
@ExcludeBinary("java_cup")
@ExcludeBinary("java_cup-javadoc")
@ExcludeBinary("java_cup-manual")
@ExcludeBinary("antlr-C\\+\\+")
public void saneName(RpmInfo rpm) {
assertThat("Package name is sane", rpm.getName(), matchesRegex("[a-z][a-z0-9.-]+[a-z0-9]"));
}
@PackageTest
@ExcludeBinary("maven-lib")
@ExcludeBinary("sisu")
@ExcludeBinary("javapackages-bootstrap")
public void bundledProvides(RpmInfo rpm) {
assertThat("No bundled(*) provides", rpm, not(provides("bundled\\(.*\\)")));
}
@PackageTest
@IncludeBinary
public void binaryVersionMatchesSourceVersion(RpmInfo rpm) {
Pattern srpmPattern = Pattern.compile("^(.+)-([^-]+)-([^-]+).src.rpm$");
Matcher matcher = srpmPattern.matcher(rpm.getSourceRPM());
assertThat("SRPM name matches regex", matcher.matches(), is(true));
assertThat("Binary package Version matches SRPM Version", rpm.getVersion(), is(matcher.group(2)));
assertThat("Binary package Release matches SRPM Release", rpm.getRelease(), is(matcher.group(3)));
}
@PackageTest
@IncludeBinary
public void noConflicts(RpmInfo rpm) {
assertThat("Packages have no explicit Conflicts", rpm, not(conflicts(".*")));
}
}

View file

@ -1,49 +0,0 @@
package tests;
import static io.kojan.runit.api.RUnit.*;
import static org.hamcrest.Matchers.*;
import java.nio.file.Path;
import java.util.Arrays;
import org.junit.jupiter.api.DisplayName;
import io.kojan.javadeptools.rpm.*;
import io.kojan.runit.api.*;
@DisplayName("/file")
public class FileCheck {
@FileTest
@IncludeRegularFile
@ExcludeBinary(".*-debuginfo")
public void elvesLiveInUserLib(RpmInfo rpm, Path path, byte[] data, RpmFile entry) {
assumeThat("File is not too small to be ELF", entry.getSize(), is(greaterThanOrEqualTo(4L)));
assumeThat("ELF magic is present", Arrays.copyOfRange(data, 0, 4), is(new byte[] { 0x7f, 'E', 'L', 'F' }));
assertThat("ELF files must be under /usr/lib", path.toString(), startsWith("/usr/lib/"));
assertThat("Noarch packages must not contain ELF files", rpm.getArch(), is(not("noarch")));
}
@FileTest
@IncludeBinary
@ExcludeBinary(".*-debuginfo")
public void modeTest(RpmInfo rpm, Path path, byte[] data, RpmFile entry) throws Exception {
String mode = Integer.toOctalString((int) entry.getMode() & 07777);
if (entry.isRegularFile()) {
if (entry.getSize() >= 4 && data[0] == 0x7f && data[1] == 'E' && data[2] == 'L' && data[3] == 'F') {
assertThat("ELF files must have mode 755", mode, is("755"));
} else if (entry.getSize() >= 2 && data[0] == '#' && data[1] == '!') {
assertThat("Files with shebang must have mode 755", mode, is("755"));
} else if (entry.getSize() == 0) {
assertThat("Empty files must have mode 644 or 0", mode, either(is("644")).or(is("0")));
} else {
assertThat("Other regular files must have mode 644", mode, is("644"));
}
} else if (entry.isDirectory()) {
assertThat("Directories must have mode 755", mode, is("755"));
} else if (!entry.isSymbolicLink()) {
assertThat("Not a regular file, not a symlink and not a directory", false, is(true));
}
}
}

View file

@ -1,37 +0,0 @@
package tests;
import static io.kojan.runit.api.RUnit.*;
import static io.kojan.runit.api.matcher.RUnitMatchers.*;
import static org.hamcrest.Matchers.*;
import java.nio.charset.StandardCharsets;
import org.junit.jupiter.api.DisplayName;
import io.kojan.javadeptools.rpm.RpmInfo;
import io.kojan.runit.api.ExcludeFileName;
import io.kojan.runit.api.ExcludeSource;
import io.kojan.runit.api.ExcludeSymlink;
import io.kojan.runit.api.FileTest;
import io.kojan.runit.api.IncludeFileName;
@DisplayName("/jpackage_script")
public class JPackageScriptCheck {
@FileTest
@IncludeFileName("/usr/bin/.*")
@ExcludeSource
@ExcludeSymlink
@ExcludeFileName("/usr/bin/ant")
public void testJPackageScript(RpmInfo rpm, byte[] bytes) {
assumeThat("The binary is a script with a shebang", bytes.length, greaterThan(1));
assumeThat("The binary is a script with a shebang", bytes[0], is((byte)'#'));
assumeThat("The binary is a script with a shebang", bytes[1], is((byte)'!'));
String content = new String(bytes, StandardCharsets.UTF_8);
assumeThat("The script was generated with %jpackage_script", content,
containsString(". /usr/share/java-utils/java-functions\n"));
assertThat("The script explicitly sets JAVA_HOME to Java 21", content,
containsString("\nJAVA_HOME=\"${JAVA_HOME:-/usr/lib/jvm/jre-21-openjdk}\"\n"));
assertThat("Requires on java-21-openjdk-headless are present", rpm, requires("java-21-openjdk-headless"));
assertThat("Requires on javapackages-tools are present", rpm, requires("javapackages-tools"));
}
}

View file

@ -1,43 +0,0 @@
package tests;
import static io.kojan.runit.api.RUnit.*;
import static org.hamcrest.Matchers.*;
import java.io.ByteArrayInputStream;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.jar.Attributes;
import java.util.jar.JarInputStream;
import java.util.jar.Manifest;
import org.junit.jupiter.api.DisplayName;
import io.kojan.javadeptools.rpm.*;
import io.kojan.runit.api.*;
@DisplayName("/jar")
public class JarCheck {
@FileTest
@IncludeFileName("/usr/(share|lib)/java/.*\\.jar")
@ExcludeSource
@ExcludeSymlink
public void testJarSanity(RpmInfo rpm, Path path, byte[] content, RpmFile entry) throws Exception {
assertThat("JAR file must be a regular file", entry.isRegularFile(), is(true));
assertThat("JAR file mode must be 644", Integer.toOctalString((int) entry.getMode() & 07777), is("644"));
assertThat("JAR file must not be empty", entry.getSize(), greaterThan(10L));
assertThat("JAR file has PKZIP magic number", (char) content[0], is('P'));
assertThat("JAR file has PKZIP magic number", (char) content[1], is('K'));
try (JarInputStream jis = new JarInputStream(new ByteArrayInputStream(content))) {
Manifest mf = jis.getManifest();
assertThat("JAR must have manifest", mf, not(nullValue()));
Attributes attrs = mf.getMainAttributes();
assertThat("JAR must have main attritutes", attrs, not(nullValue()));
for (String attr : Arrays.asList("Javapackages-GroupId", "Javapackages-ArtifactId",
"Javapackages-Version")) {
String val = attrs.getValue(attr);
assertThat("JAR must have " + attr + " manifest attribute", val, not(nullValue()));
}
}
}
}

View file

@ -1,28 +0,0 @@
package tests;
import static io.kojan.runit.api.RUnit.*;
import static org.hamcrest.Matchers.*;
import java.nio.file.Path;
import org.junit.jupiter.api.DisplayName;
import io.kojan.javadeptools.rpm.*;
import io.kojan.runit.api.*;
@DisplayName("/javadoc")
public class JavadocCheck {
@PackageTest
@IncludeBinary(".*-javadoc")
public void testJavadocNoarch(RpmInfo rpm) {
assertThat("Javadoc package is noarch", rpm.getArch(), is("noarch"));
}
@FileTest
@IncludeBinary(".*-javadoc")
public void javadocFiles(RpmInfo rpm, Path path) {
assertThat("Javadoc package contains only javadoc content", path.toString(),
anyOf(startsWith("/usr/share/javadoc/"), startsWith("/usr/share/licenses/")));
}
}

View file

@ -1,188 +0,0 @@
package tests;
import static io.kojan.runit.api.RUnit.*;
import static org.junit.jupiter.api.Assertions.fail;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.hamcrest.Description;
import org.hamcrest.TypeSafeMatcher;
import org.junit.jupiter.api.DisplayName;
import io.kojan.javadeptools.rpm.*;
import io.kojan.runit.api.*;
@DisplayName("/license")
public class LicenseCheck {
// General-purpose licenses that are approved for everything, including code.
// See: https://docs.fedoraproject.org/en-US/legal/allowed-licenses/
private static final Set<String> GLOBAL_ALLOWED_LICENSES = Set.of( //
// General-purpose permissive licenses
"0BSD", //
"Apache-1.1", //
"Apache-2.0", //
"BSD-2-Clause", //
"BSD-3-Clause", //
"BSD-3-Clause-Sun", //
"CPL-1.0", //
"EPL-1.0", //
"EPL-2.0", //
"ISC", //
"MIT", //
"Plexus", //
"SMLNJ", //
"Saxpath", //
"xpp", //
"W3C", //
// Very permissive licenses, close to public domain
"ANTLR-PD", //
"CC0-1.0", //
"SAX-PD-2.0", //
// GPL is only allowed with classpath exception,
// and only as alternative to permissive licenses
"EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0", //
"EPL-2.0 OR BSD-3-Clause OR GPL-2.0-only WITH Classpath-exception-2.0", //
// Likewise LGPL
"LGPL-2.0-or-later OR Apache-2.0", //
"Apache-2.0 OR LGPL-2.0-or-later" //
);
// Public domain is valid only for packages that were reviewed by Fedora legal
// and which were added to public-domain-text.txt at
// https://gitlab.com/fedora/legal/fedora-license-data
private static final Set<String> PUBLIC_DOMAIN_PACKAGES = Set.of( //
"aopalliance", //
"plexus-utils", //
"plexus-utils4", //
"xz-java", //
"javapackages-bootstrap" // special case, as it it bundles all the above
);
// Allowed content licenses, in addition to GLOBAL_ALLOWED_LICENSES
private static final Set<String> CONTENT_ALLOWED_LICENSES = Set.of( //
"GPL-2.0-or-later", //
"LGPL-2.1-or-later", //
"AFL-2.0", //
"CC-BY-2.5" //
);
@PackageTest
@IncludeBinary
public void testLicense(RpmInfo rpm) {
// Licenses that are allowed for particular package. This may be a superset of
// GLOBAL_ALLOWED_LICENSES, for example if the package is known to be docs-only,
// content licenses may be allowed in addition to general-purpose licenses.
Set<String> packageAllowedLicenses = new LinkedHashSet<String>(GLOBAL_ALLOWED_LICENSES);
if (PUBLIC_DOMAIN_PACKAGES.contains(rpm.getSourceName())) {
packageAllowedLicenses.add("LicenseRef-Fedora-Public-Domain");
}
if (rpm.getName().endsWith("-javadoc") || rpm.getName().endsWith("-manual")) {
packageAllowedLicenses.addAll(CONTENT_ALLOWED_LICENSES);
}
assertThat("License is allowed", rpm.getLicense(), new LicenseMatcher(packageAllowedLicenses));
}
}
class LicenseMatcher extends TypeSafeMatcher<String> {
private static final Pattern ID_REGEX = Pattern.compile("([a-zA-Z0-9.-]+).*");
private final Set<String> packageAllowedLicenses;
private String expression;
private int parserPos;
public LicenseMatcher(Set<String> packageAllowedLicenses) {
this.packageAllowedLicenses = packageAllowedLicenses;
}
private void parseError(String msg) {
StringBuilder sb = new StringBuilder();
sb.append("Syntax error - " + msg + "\n");
sb.append(" SPDX expression: \"" + expression + "\"\n");
sb.append(" Location: here ---" + new String("-").repeat(parserPos) + "^");
fail(sb.toString());
}
private void parseEof() {
if (parserPos != expression.length()) {
parseError("Expected EOF");
}
}
private boolean matches(String op) {
if (expression.substring(parserPos).startsWith(op)) {
parserPos += op.length();
return true;
}
return false;
}
private boolean allowed(int beginPos) {
String expr = expression.substring(beginPos, parserPos);
return packageAllowedLicenses.contains(expr);
}
private void parseAtom() {
Matcher matcher = ID_REGEX.matcher(expression.substring(parserPos));
if (!matcher.matches()) {
parseError("Expected identifier");
}
String tag = matcher.group(1);
parserPos += tag.length();
}
private boolean parsePrimary() {
if (matches("(")) {
boolean allowed = parseUnion();
if (!matches(")")) {
parseError("Unclosed parenthesis");
}
return allowed;
}
int beginPos = parserPos;
parseAtom();
if (matches(" WITH ")) {
parseAtom();
}
return allowed(beginPos);
}
private boolean parseIntersection() {
int beginPos = parserPos;
boolean allowed = parsePrimary();
if (matches(" AND ")) {
allowed &= parseIntersection();
}
return allowed || allowed(beginPos);
}
private boolean parseUnion() {
int beginPos = parserPos;
boolean allowed = parseIntersection();
if (matches(" OR ")) {
allowed &= parseUnion();
}
return allowed || allowed(beginPos);
}
@Override
protected boolean matchesSafely(String license) {
expression = license;
parserPos = 0;
boolean allowed = parseUnion();
parseEof();
return allowed;
}
@Override
public void describeTo(Description description) {
description.appendText("approved license");
}
}