Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
105 changes: 94 additions & 11 deletions .github/workflows/continuousIntegration.yml
Original file line number Diff line number Diff line change
@@ -1,29 +1,112 @@
name: Continuous Integration

on:
pull_request: ~
pull_request: ~
push:
branches:
- main
- dev

# Docker images for various Racket versions available on DockerHub
# as racket/racket:<version>
# (https://github.com/racket/docker)
# We use features that require at least 8.4; try to use latest.

jobs:
forge-tests:
check-test-coverage:
runs-on: ubuntu-latest
name: check-test-coverage
steps:
- name: Checkout repo
uses: actions/checkout@v4

- name: Verify all test subdirs are in CI matrix
run: |
# Extract all tests/* paths mentioned in the workflow file itself.
# This avoids maintaining a separate list — the matrix IS the source of truth.
workflow=".github/workflows/continuousIntegration.yml"
covered=$(grep -oE 'tests/[a-zA-Z0-9_-]+(/[a-zA-Z0-9_-]+)?' "$workflow" \
| sort -u)

# Check tests/forge/ subdirectories
actual=$(cd forge && find tests/forge -mindepth 1 -maxdepth 1 -type d | sort)
missing=$(comm -23 <(echo "$actual") <(echo "$covered"))
if [ -n "$missing" ]; then
echo "ERROR: tests/forge/ subdirectories not covered by CI:"
echo "$missing"
echo ""
echo "Add them to a test-dir entry in $workflow"
exit 1
fi

# Check top-level test directories too
actual_top=$(cd forge && find tests -mindepth 1 -maxdepth 1 -type d | sort)
missing_top=$(comm -23 <(echo "$actual_top") <(echo "$covered"))
if [ -n "$missing_top" ]; then
echo "ERROR: top-level test directories not covered by CI:"
echo "$missing_top"
echo ""
echo "Add them to a test-dir entry in $workflow"
exit 1
fi

echo "All test directories are covered by CI."

test-forge:
needs: check-test-coverage
runs-on: ubuntu-latest
container: docker://karimmouline/forge-dev-img:latest
strategy:
fail-fast: false
matrix:
include:
- name: forge-1
test-dir: tests/forge/other
- name: forge-2
test-dir: tests/forge/bounds tests/forge/domains tests/forge/eval-model tests/forge/examples tests/forge/expressions tests/forge/formulas tests/forge/fuzz tests/forge/ints tests/forge/library tests/forge/relations tests/forge/sigs tests/forge/target
- name: forge-core
test-dir: tests/forge-core tests/forge-functional
- name: smt
test-dir: tests/smt
- name: other
test-dir: tests/error tests/froglet tests/srclocs tests/temporal
name: test-${{ matrix.name }}
steps:
- name: Checkout repo
uses: actions/checkout@v3
uses: actions/checkout@v4

- name: Setup Racket
uses: Bogdanp/setup-racket@v1.14
with:
version: '8.15'

- name: Setup Java
uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: '17'

- name: Install system dependencies
run: |
sudo apt-get update -qq
sudo apt-get install -y -qq libcairo2-dev libpango1.0-dev libgdk-pixbuf-2.0-dev

- name: Install cvc5
if: matrix.name == 'smt'
run: |
wget -q https://github.com/cvc5/cvc5/releases/download/cvc5-1.2.0/cvc5-Linux-x86_64-static.zip
unzip -q cvc5-Linux-x86_64-static.zip
echo "$PWD/cvc5-Linux-x86_64-static/bin" >> $GITHUB_PATH

- name: Cache Racket packages
uses: actions/cache@v4
with:
path: ~/.local/share/racket
key: racket-pkgs-${{ hashFiles('forge/info.rkt') }}
restore-keys: racket-pkgs-

- name: Install Forge
run: |
raco pkg install --auto --no-docs ./forge
raco pkg update --auto --no-docs --batch ./forge 2>/dev/null || raco pkg install --auto --no-docs ./forge

- name: Run tests
run: |
cd forge/
chmod +x run-tests.sh
./run-tests.sh tests/
for dir in ${{ matrix.test-dir }}; do
./run-tests.sh "$dir"
done
10 changes: 9 additions & 1 deletion forge/domains/abac/runner.rkt
Original file line number Diff line number Diff line change
Expand Up @@ -198,10 +198,18 @@
(list (= r (atom 'True)))]
[(equal? (relation-name r) "Request")
(list (= r (atom 'Request)))]
; Boolean fields (target sig is True)
[(equal? (relation-name r) "training")
(list (in r (-> (+ atoms) (atom 'True))))]
[(equal? (relation-name r) "audit")
(list (in r (-> (+ atoms) (atom 'True))))]
; owner maps File -> Subject; Subject's upper bound is the universe atoms
[(equal? (relation-name r) "owner")
(list (in r (-> (+ atoms) (+ atoms))))]
; Everything else:
[(equal? 1 (relation-arity r))
(list (in r (+ atoms)))]
[(equal? 2 (relation-arity r))
[(equal? 2 (relation-arity r))
(list (in r (-> (+ atoms) (+ (atom 'True) (+ atoms)))))]
[else
(raise-user-error (format "Error: relation ~a had invalid arity" r))]))
Expand Down
4 changes: 0 additions & 4 deletions forge/domains/abac/tests/info.rkt

This file was deleted.

1 change: 1 addition & 0 deletions forge/domains/crypto/examples/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
CI regression tests derived from these examples live in forge/tests/forge/domains/crypto/.
4 changes: 2 additions & 2 deletions forge/info.rkt
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

(define collection "forge")

(define version "5.0.1")
(define version "5.1")

(define deps '("base"
"syntax-classes" ; used in parser and expander
Expand Down Expand Up @@ -40,7 +40,7 @@
; This includes outdated modules but also examples.
(define compile-omit-paths '("example" "examples" "doc" "tests" "check-ex-spec/demo"
"OLD" "pardinus-cli/out" "kodkod-cli/out" "check-ex-spec/examples"
"amalgam/tests" "amalgam" "domains/crypto/examples" "domains/abac/tests"
"amalgam/tests" "amalgam" "domains/crypto/examples"
))


Expand Down
Binary file removed forge/pardinus-cli/jar/libminisatprover.so
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
173 changes: 173 additions & 0 deletions forge/pardinus-cli/jar/native/test-linux-x86_64.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
#!/bin/bash
# Test native SAT solver libraries on Linux x86_64 using Docker.
#
# Verifies that each .so file:
# 1. Is a valid x86_64 ELF binary
# 2. Has portable glibc requirements (≤ 2.31)
# 3. Exports expected JNI symbols
# 4. Actually loads in a JVM and can create/free a solver instance
#
# Uses Ubuntu 20.04 (same glibc as our build target) with OpenJDK.
# No Racket needed — tests JNI loading directly via Java.
#
# Usage:
# ./test-linux-x86_64.sh # Run all checks
# ./test-linux-x86_64.sh --shell # Drop into container for debugging

set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
FORGE_ROOT="$(cd "$SCRIPT_DIR/../../../.." && pwd)"
IMAGE="ubuntu:20.04"
PLATFORM="linux/amd64"

if [ "${1:-}" = "--shell" ]; then
echo "Dropping into test container."
echo "Forge repo mounted at /forge, native libs at /libs, JARs at /jars"
echo ""
docker run --rm -it --platform="$PLATFORM" \
-v "$SCRIPT_DIR/linux-x86_64":/libs \
-v "$FORGE_ROOT/forge/pardinus-cli/jar":/jars \
-w /tmp \
"$IMAGE" bash
exit 0
fi

TEST_SCRIPT='#!/bin/bash
set -euo pipefail

export DEBIAN_FRONTEND=noninteractive TZ=UTC
apt-get update -qq && apt-get install -y -qq openjdk-17-jdk binutils file > /dev/null 2>&1
export JAVA_HOME=/usr/lib/jvm/java-17-openjdk-amd64

echo "=== Platform ==="
uname -m
java -version 2>&1 | head -1

echo ""
echo "=== Library checks ==="
ALL_OK=true

for f in /libs/*.so; do
[ -f "$f" ] || continue
name=$(basename "$f" .so)
name=${name#lib} # strip "lib" prefix
echo ""
echo "--- $name ---"

# Check: 64-bit ELF
arch=$(file "$f")
if echo "$arch" | grep -q "ELF 64-bit.*x86-64"; then
echo " arch: x86_64 OK"
else
echo " arch: FAIL - $arch"
ALL_OK=false
continue
fi

# Check: glibc version requirements
max_glibc=$(objdump -T "$f" 2>/dev/null | grep -oP "GLIBC_\K[0-9.]+" | sort -V | tail -1)
if [ -z "$max_glibc" ]; then
echo " glibc: (no GLIBC symbols - statically linked or pure C)"
else
echo " glibc: requires <= $max_glibc"
fi

# Check: JNI symbols
jni_count=$(nm -D "$f" 2>/dev/null | grep -c "T Java_" || true)
echo " jni: $jni_count exported symbols"
if [ "$jni_count" -eq 0 ]; then
echo " WARN: no JNI symbols found"
ALL_OK=false
fi

# Check: runtime dependencies
deps=$(ldd "$f" 2>/dev/null | grep -v "linux-vdso\|ld-linux" | awk "{print \$1}" | tr "\n" " ")
echo " deps: $deps"
done

echo ""
echo "=== JVM load test ==="

# Write a Java test that loads each native library and tries to instantiate a solver
mkdir -p /tmp/jnitest
cat > /tmp/jnitest/TestSolvers.java << JAVAEOF
import java.io.File;

public class TestSolvers {
public static void main(String[] args) {
String libDir = args.length > 0 ? args[0] : "/libs";
String jarDir = args.length > 1 ? args[1] : "/jars";

// Map of library name -> JNI class that loads it
String[][] solvers = {
{"glucose", "kodkod.engine.satlab.Glucose"},
{"minisat", "kodkod.engine.satlab.MiniSat"},
{"minisatprover", "kodkod.engine.satlab.MiniSatProver"},
{"lingeling", "kodkod.engine.satlab.Lingeling"},
};

int passed = 0, failed = 0;

for (String[] solver : solvers) {
String libName = solver[0];
String className = solver[1];
System.out.print(libName + ": ");
try {
// Load the native library directly
System.loadLibrary(libName);
System.out.print("loaded");

// Try to instantiate the solver via the Java wrapper
Class<?> cls = Class.forName(className);
Object instance = cls.getMethod("instance").invoke(
cls.getField(capitalize(libName)).get(null)
);
System.out.println(" ... SKIP class test (need SATFactory)");
passed++;
} catch (UnsatisfiedLinkError e) {
System.out.println("LOAD FAILED: " + e.getMessage());
failed++;
} catch (Exception e) {
// Library loaded but class instantiation failed - that is
// fine, it means the .so is valid. The class test needs
// the full kodkod classpath which we test separately.
System.out.println(" OK (JNI load succeeded)");
passed++;
}
}

System.out.println();
System.out.println(passed + " passed, " + failed + " failed");
System.exit(failed > 0 ? 1 : 0);
}

static String capitalize(String s) {
return s.substring(0,1).toUpperCase() + s.substring(1);
}
}
JAVAEOF

cd /tmp/jnitest
javac TestSolvers.java
java -Djava.library.path=/libs -cp . TestSolvers /libs /jars
RESULT=$?

echo ""
if [ "$ALL_OK" = true ] && [ $RESULT -eq 0 ]; then
echo "=== ALL CHECKS PASSED ==="
else
echo "=== SOME CHECKS FAILED ==="
exit 1
fi
'

echo "Testing native solver libraries for Linux x86_64"
echo "Image: $IMAGE ($PLATFORM)"
echo ""

docker run --rm --platform="$PLATFORM" \
-v "$SCRIPT_DIR/linux-x86_64":/libs:ro \
-v "$FORGE_ROOT/forge/pardinus-cli/jar":/jars:ro \
-w /tmp \
"$IMAGE" bash -c "$TEST_SCRIPT"
Binary file removed forge/pardinus-cli/jar/win64/minisatprover.dll
Binary file not shown.
20 changes: 12 additions & 8 deletions forge/pardinus-cli/server/pardinus-server.rkt
Original file line number Diff line number Diff line change
Expand Up @@ -24,17 +24,21 @@
(find-executable-path (if windows? "java.exe" "java")))]
[path-separator (if windows? ";" ":")]
[cp (foldl string-append "" (add-between (map path->string jars) path-separator))]
;[lib (path->string (build-path pardinus/jni (case (system-type)
;[(macosx) "darwin_x86_64"]
;[(unix) "linux_x86_64"]
;[(windows) "win_x86_64"])))]
;[-Djava.library.path (string-append "-Djava.library.path=" lib)]
[native-subdir (case (system-type)
[(macosx) "macos"]
[(unix) (if (equal? (system-type 'word) 64)
"linux-x86_64"
"linux-x86")]
[(windows) (if (equal? (system-type 'word) 64)
"windows-x86_64"
"windows-x86")])]
[native-dir (build-path pardinus/jar "native" native-subdir)]
[error-out (build-path (find-system-path 'home-dir) "forge-pardinus-error-output.txt")])
(when (> (get-verbosity) VERBOSITY_LOW)

(when (> (get-verbosity) VERBOSITY_LOW)
(printf " Starting solver process. subtype: ~a~n" solver-subtype))

(define lib-path (string-append "-Djava.library.path=" (path->string pardinus/jar)))
(define lib-path (string-append "-Djava.library.path=" (path->string native-dir)))
(define solver-subtype-str (cond [(equal? solver-subtype 'target) "-target-oriented"]
[(equal? solver-subtype 'temporal) "-temporal"]
[(equal? solver-subtype 'default) ""]
Expand Down
Loading