Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
ad42a88
add: .idea files to gitignore
Nigma-Ks Jan 9, 2026
b862616
feat(dockerfile): add build PySymGym dockerfile
Nigma-Ks Jan 9, 2026
f7f9218
fix: make building on macos dockerfile
Nigma-Ks Jan 12, 2026
7ec2f7a
fix: add runstrat test and fix errors occured
Nigma-Ks Jan 13, 2026
ab1faa4
add func that runs test case in docker
Nigma-Ks Feb 10, 2026
715cb65
feat(run_runstrat): run ExecutionTreeContributedCoverage strategy and…
Nigma-Ks Feb 17, 2026
d35f886
remove runstrat test from docker
Nigma-Ks Feb 17, 2026
4186072
feat(frontend form)
Nigma-Ks Feb 18, 2026
e97dabd
add backend-frontend connection to upload onnx model
Nigma-Ks Feb 18, 2026
6604dcd
refactor net7 to net8, run runstrat ai strat
Nigma-Ks Feb 18, 2026
efad1b8
feat(frontend): form data uploads on submit
Nigma-Ks Feb 19, 2026
85dccff
new data on new submit
Nigma-Ks Feb 19, 2026
6c897f2
feat: function to send results on email
Nigma-Ks Feb 21, 2026
12192f0
feat add building container and loading dataset script
Nigma-Ks Feb 21, 2026
b9f3910
add email validation
Nigma-Ks Feb 21, 2026
12f6bfd
feat: comparison with baseline, field experiment name in form
Nigma-Ks Feb 21, 2026
32f0a55
refactor: runstrat and compstrat cmds to template methods
Nigma-Ks Feb 23, 2026
25dc258
fix model onnx uploads with model.onnx name
Nigma-Ks Feb 23, 2026
d685996
fix docker cmd
Nigma-Ks Feb 23, 2026
3cf7ef7
refactor: split main logic into separate services
Nigma-Ks Feb 23, 2026
d237399
refactor: move image name to build file and make it const
Nigma-Ks Feb 24, 2026
f409623
feat: unique directories for each launch
Nigma-Ks Feb 24, 2026
f9372a1
load dataset
Nigma-Ks Feb 25, 2026
3d8dd92
remove tmp dirs after submit handling
Nigma-Ks Feb 26, 2026
45c31e6
add parallel execution of pipeline using Celery
Nigma-Ks Feb 26, 2026
3194fe4
add tmp dir to gitignore
Nigma-Ks Feb 26, 2026
55cf00d
docker build no cache to update pysymgym
Nigma-Ks Feb 26, 2026
b154e18
refactor Methods class, move build and fetch functions to app_setup
Nigma-Ks Feb 28, 2026
2259ff3
feat add tests
Nigma-Ks Feb 28, 2026
a9883ee
add requirements file
Nigma-Ks Mar 2, 2026
7161f36
add test workflow: build project without docker and run some tests
Nigma-Ks Mar 5, 2026
7c39925
fix remove pytest args
Nigma-Ks Mar 5, 2026
7c0cc41
fix: resolve module imports in tests
Nigma-Ks Mar 5, 2026
48181b6
add building pysymgym workflow
Nigma-Ks Mar 5, 2026
7ddae70
fix: resolve backend module path
Nigma-Ks Mar 5, 2026
345af3d
fix misprint
Nigma-Ks Mar 5, 2026
00200e9
run all tests in docker.yml
Nigma-Ks Mar 5, 2026
9eeb486
add .env file for tests
Nigma-Ks Mar 5, 2026
bc45f70
refactor change docker test result dir to pytest tmp_path
Nigma-Ks Mar 5, 2026
0ef4977
docs: add detailed README with installation instructions
Nigma-Ks Mar 5, 2026
96159f4
refactor(tests): pytest tmp_path instead of TMP_DIR
Nigma-Ks Mar 5, 2026
8231805
refactor(Methods): change Methods methods names
Nigma-Ks Mar 5, 2026
785f136
remove workflow without docker
Nigma-Ks Mar 5, 2026
5233bbb
fetch_dataset function uploads dataset from docker image, ruff format
Nigma-Ks Mar 6, 2026
dc069c4
refactor: join paths
Nigma-Ks Mar 6, 2026
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
36 changes: 36 additions & 0 deletions .github/workflows/build_and_test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
name: Build project and run tests

on: [ push, pull_request ]

jobs:
build-and-test:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Set up Python 3.14
uses: actions/setup-python@v5
with:
python-version: '3.14'
cache: 'pip'

- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt

- name: Build dockerfile and update dataset
run: |
python -m backend.launch_service.app_setup

- name: Create .env file for tests
run: |
cat > .env << EOF
EMAIL=test@example.com
APP_PASSWORD=test_password_123
EOF

- name: Run Python tests
run: |
python -m pytest -v
21 changes: 17 additions & 4 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ celerybeat.pid
*.sage.py

# Environments
.env
backend/.env
.envrc
.venv
env/
Expand Down Expand Up @@ -171,7 +171,7 @@ cython_debug/
# PyCharm
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# and can be added to the global gitignore or merged into this file_utils. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/

Expand All @@ -184,14 +184,14 @@ cython_debug/
# Visual Studio Code
# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
# that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
# and can be added to the global gitignore or merged into this file. However, if you prefer,
# and can be added to the global gitignore or merged into this file_utils. However, if you prefer,
# you could uncomment the following to ignore the entire vscode folder
# .vscode/

# Ruff stuff:
.ruff_cache/

# PyPI configuration file
# PyPI configuration file_utils
.pypirc

# Cursor
Expand All @@ -205,3 +205,16 @@ cython_debug/
marimo/_static/
marimo/_lsp/
__marimo__/

#IDE
.idea/
.DS_Store

# runstrat artifacts and config
.env
backend/tmp

#Frontend
node_modules/
frontend/.gitignore
frontend/README.md
71 changes: 70 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,71 @@
# PySymBench
Infrastructure for models comparison and evalustion
Infrastructure for **model comparison and evaluation in symbolic execution workflows**.

This project is a **local web application** designed to compare symbolic execution results of an uploaded trained model (in `.onnx` format) on a selected dataset with a **baseline symbolic execution approach (non-AI)**.

The system uses **PySymGym tools** to run symbolic execution on the dataset and evaluate the results. After execution completes, the results are sent to the **email address you provide**.

# Installation

The repository contains **both frontend and backend components**, and **both must be launched** for the application to work.

---

## Email Communication (Gmail)

To enable email delivery of results, create a `.env` file containing your Gmail credentials:

```
EMAIL=your_email@gmail.com
APP_PASSWORD=your_app_password
```

`EMAIL` — your Gmail address
`APP_PASSWORD` — your Gmail **App Password** (not your regular account password)

---

## Backend Setup

1. Install **Python 3.14** and **Docker**, then install the project dependencies:

```
pip install -r requirements.txt
```

2. Run the application setup script (this builds a Docker container with the **PySymGym repository** and downloads the required dataset):

```
python -m backend.launch_service.app_setup
```

3. Start the **Celery broker (Redis)**:

```
docker run --name redis-for-celery -p 6379:6379 -d redis
```

4. Start the **Celery worker** and the **application server**:

```
celery -A backend.utils.task worker --loglevel=info && uvicorn backend.main:app
```

---

## Frontend Setup

1. Install **Node.js** with **npm**.

2. Install frontend dependencies:

```
cd frontend
npm install
```

3. Start the frontend development server:

```
npm run build
```
Empty file added backend/__init__.py
Empty file.
Empty file added backend/config/__init__.py
Empty file.
32 changes: 32 additions & 0 deletions backend/config/paths.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import os


def get_tmp_thread_files(uid):
return [get_thread_filepath(uid, UPLOAD_DIR), get_thread_filepath(uid, RESULTS_DIR)]


def get_thread_filepath(uid, filepath):
base_prefix = TMP_FILE_DIR + "/"
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can use os.sep here.

new_prefix = f"{TMP_FILE_DIR}/{uid}"
return filepath.replace(base_prefix, new_prefix)


DOCKER_DIR = os.path.join(os.getcwd(), "docker")
BASE_DIR = os.path.join(os.getcwd(), "backend")
TMP_FILE_DIR = os.path.join(BASE_DIR, "tmp")

RESOURCES_DIR = os.path.join(BASE_DIR, "resources")
UPLOAD_DIR = os.path.join(TMP_FILE_DIR, "uploads")
RESULTS_DIR = os.path.join(TMP_FILE_DIR, "results")

DATASET_FILE = os.path.join(RESOURCES_DIR, "dataset.json")
LAUNCH_INFO_FILE = os.path.join(UPLOAD_DIR, "launch_info.csv")
MODEL_ONNX_FILE = os.path.join(UPLOAD_DIR, "model.onnx")
METHODS_TS_FILE = os.path.join(
BASE_DIR, "../frontend/src/components/components/Methods.ts"
)
COMPSTRAT_RESULTS_DIR = os.path.join(RESULTS_DIR, "compstrat_results")
ARTIFACTS_AI_CSV_FILE = os.path.join(RESULTS_DIR, "artifacts_run_ai/AI.csv")
ARTIFACTS_BASELINE_CSV_FILE = os.path.join(
RESULTS_DIR, "artifacts_run_baseline/ExecutionTreeContributedCoverage.csv"
)
31 changes: 31 additions & 0 deletions backend/config/tests/test_filepaths.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import pytest

from backend.config.paths import (
get_tmp_thread_files,
RESULTS_DIR,
UPLOAD_DIR,
get_thread_filepath,
TMP_FILE_DIR,
)


@pytest.mark.parametrize(
"uid, filepath, expected",
[
("", RESULTS_DIR, RESULTS_DIR),
("123", UPLOAD_DIR, TMP_FILE_DIR + "/123uploads"),
],
)
def test_get_thread_filepath(uid, filepath, expected):
assert get_thread_filepath(uid, filepath) == expected


@pytest.mark.parametrize(
"uid, expected",
[
("", [UPLOAD_DIR, RESULTS_DIR]),
("123", [TMP_FILE_DIR + "/123uploads", TMP_FILE_DIR + "/123results"]),
],
)
def test_get_created_tmp_files(uid, expected):
assert get_tmp_thread_files(uid) == expected
Empty file added backend/file_utils/__init__.py
Empty file.
16 changes: 16 additions & 0 deletions backend/file_utils/csv_methods_writer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import csv


def write_launch_info_to_csv(
*,
parsed_methods: list[str],
output_file: str,
) -> None:
with open(output_file, "w") as csvfile:
writer = csv.writer(csvfile)
writer.writerow(["dll", "method"])

for item in parsed_methods:
if "," in item:
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When is there no comma in the item? Does it really not need processing?

dll, method = item.split(",", 1)
writer.writerow([dll, method])
16 changes: 16 additions & 0 deletions backend/file_utils/files.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import os
import shutil
from fastapi import UploadFile


def save_upload_file(file: UploadFile, dest_filepath: str) -> None:
os.makedirs(os.path.dirname(dest_filepath), exist_ok=True)
with open(dest_filepath, "wb") as f:
while chunk := file.file.read(1024 * 1024):
f.write(chunk)


def reset_dirs(paths: list) -> None:
for path in paths:
if os.path.exists(path):
shutil.rmtree(path)
72 changes: 72 additions & 0 deletions backend/file_utils/tests/test_file_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import pytest
import os
import csv

from backend.config.paths import TMP_FILE_DIR
from backend.file_utils.csv_methods_writer import write_launch_info_to_csv
from backend.file_utils.files import reset_dirs, save_upload_file
from unittest.mock import Mock
from fastapi import UploadFile
from io import BytesIO


@pytest.mark.parametrize(
"filepaths",
[
([TMP_FILE_DIR + "/test"]),
([TMP_FILE_DIR + "/test1", TMP_FILE_DIR + "/test2"]),
],
)
def test_removing_tmp_files(filepaths):
for path in filepaths:
os.makedirs(path)
reset_dirs(filepaths)
for path in filepaths:
assert not os.path.exists(path)


def test_save_upload_file(tmp_path):
test_content = b"Hello, World!" * 1000
dest_path = tmp_path / "test.txt"

mock_file = BytesIO(test_content)
upload_file = Mock(spec=UploadFile)
upload_file.file = mock_file

save_upload_file(upload_file, str(dest_path))

assert dest_path.exists()
with open(dest_path, "rb") as f:
saved_content = f.read()

assert saved_content == test_content


def test_write_launch_info_to_csv(tmp_path):
output_file = tmp_path / "launch_info.csv"

parsed_methods = [
"ManuallyCollected.dll,BinSearchMain",
"ManuallyCollected.dll,BellmanFord",
"ManuallyCollected.dll,BinaryMaze1BFS",
]

write_launch_info_to_csv(
parsed_methods=parsed_methods, output_file=str(output_file)
)

assert output_file.exists()

with open(output_file, "r") as f:
reader = csv.reader(f)
rows = list(reader)

assert rows[0] == ["dll", "method"]

expected_data = [
["ManuallyCollected.dll", "BinSearchMain"],
["ManuallyCollected.dll", "BellmanFord"],
["ManuallyCollected.dll", "BinaryMaze1BFS"],
]

assert rows[1:] == expected_data
Empty file.
60 changes: 60 additions & 0 deletions backend/launch_service/app_setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import os
import subprocess
from backend.config.paths import (
RESOURCES_DIR,
DATASET_FILE,
DOCKER_DIR,
METHODS_TS_FILE,
)
from backend.utils.methods_handler import Methods

IMAGE_NAME = "pysymgym-test"


def fetch_dataset(data_upload_file):
container_name = "temp-fetch-dataset"

try:
subprocess.run(
["docker", "create", "--name", container_name, IMAGE_NAME],
check=True,
capture_output=True,
)

subprocess.run(
[
"docker",
"cp",
f"{container_name}:/workspace/PySymGym/maps/DotNet/Maps/dataset.json",
data_upload_file,
],
check=True,
)

print(f"Dataset copied to {data_upload_file}")
return data_upload_file

finally:
subprocess.run(["docker", "rm", container_name], capture_output=True)


def build_container():
print(f"Building Docker image '{IMAGE_NAME}'...")
subprocess.run(
["docker", "build", "--no-cache", "-t", IMAGE_NAME, DOCKER_DIR], check=True
)
print("Docker build completed.\n")

os.makedirs(RESOURCES_DIR, exist_ok=True)


def update_frontend_selection_options(dataset_file, selection_options_file):
print("Updating frontend selection options...")
selection_tree = Methods.build_selection_tree_from_dataset(dataset_file)
Methods.save_selection_to_frontend_file(selection_tree, selection_options_file)


if __name__ == "__main__":
build_container()
fetch_dataset(DATASET_FILE)
update_frontend_selection_options(DATASET_FILE, METHODS_TS_FILE)
Loading