Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
71 commits
Select commit Hold shift + click to select a range
71f2697
adicionado nova tool para gerar testes
nathaliafab May 13, 2024
52ad4a2
gera outputs para diferentes branches
nathaliafab May 14, 2024
77a5566
alterações nas funções
nathaliafab May 17, 2024
e585dba
Add try except block on compile test suite method
nathaliafab May 28, 2024
a5aeba5
Add some functions for better compilation work
nathaliafab May 28, 2024
cec679a
removed print
nathaliafab May 29, 2024
4ee7db1
added/fixed logging
nathaliafab May 29, 2024
aa84b01
fix class and test naming
nathaliafab Jun 14, 2024
ddf12e7
change test not found result
nathaliafab Jun 14, 2024
b5c8f1b
removed unused functions and cleaned the code
nathaliafab Jun 14, 2024
655d95d
simply not include not found tests
nathaliafab Jun 14, 2024
f4a6e29
remove unused imports
nathaliafab Jun 14, 2024
1d427f1
varias coisa
nathaliafab Jun 21, 2024
4db60de
calledprocesserror
nathaliafab Aug 21, 2024
e28295d
update requirements
nathaliafab Aug 21, 2024
8a7c153
varias refatoração
nathaliafab Aug 21, 2024
973adc3
é o refatoras
nathaliafab Aug 27, 2024
b70c052
melhor logging e mudança no path do output
nathaliafab Sep 3, 2024
e25abcb
reports nao sao sobrescritos
nathaliafab Sep 3, 2024
131d001
conserto de varios bugs
nathaliafab Sep 3, 2024
e42e1b7
fix jar type search
nathaliafab Sep 17, 2024
ae8badc
update logging
nathaliafab Sep 30, 2024
660838a
calc time
nathaliafab Sep 30, 2024
6dfed76
better output
nathaliafab Sep 30, 2024
8c88634
update req
nathaliafab Oct 1, 2024
ea9df20
a
nathaliafab Oct 1, 2024
fad3c28
fix: "targets" attr type
nathaliafab Nov 19, 2024
44c3ff6
remove ignored modules from logging
nathaliafab Nov 19, 2024
7169eeb
feat(model): migrate from local codellama-7b to API-based codellama-7…
nathaliafab Nov 20, 2024
5fcc7ec
change test template logic and prompt messages
nathaliafab Dec 10, 2024
083f140
update dependencies versions
nathaliafab Jan 8, 2025
cbedd6a
fixed var types
nathaliafab Jan 8, 2025
4e2a94b
use the current repo name
nathaliafab Jan 8, 2025
0453c4d
fixed some stylistic issues
nathaliafab Jan 8, 2025
344d54d
parametrização dos dados da api
nathaliafab Mar 12, 2025
e132acc
creates a file to store compilation outputs
nathaliafab Mar 12, 2025
db8be5c
fixed get_config() function to support dicts inside dicts
nathaliafab Mar 25, 2025
9d1f85e
move functions to utils
nathaliafab Apr 15, 2025
038f41b
created api class
nathaliafab Apr 15, 2025
e8ad444
fixed scenario infos format and remove useless function
nathaliafab Apr 15, 2025
8ba504d
conserto da lógica
nathaliafab Apr 22, 2025
3f36b79
conserto de lógica + saída em arquivo json
nathaliafab Apr 22, 2025
9195e6f
small fix logging message
nathaliafab Jun 19, 2025
e17eed8
get seed from config
nathaliafab Jun 19, 2025
4e49187
remove version from requirements
nathaliafab Jun 19, 2025
94003b0
typing issues
nathaliafab Jun 19, 2025
6bc007c
change logic - special codellama
nathaliafab Jun 19, 2025
a19fb60
update config params
nathaliafab Jun 19, 2025
32da8ca
dealing with paths - evosuite
nathaliafab Jun 19, 2025
2c3237d
remove unused globals
nathaliafab Jun 20, 2025
5f38ba8
remove implicit optional
nathaliafab Jun 20, 2025
04b1d79
Merge pull request #1 from nathaliafab/new-prompts
nathaliafab Jun 20, 2025
e4643fd
Merge branch 'master' into codellama-integration
nathaliafab Jun 21, 2025
cfb8b62
Update nimrod/utils.py
nathaliafab Oct 30, 2025
433eec2
Update nimrod/test_suites_execution/test_suite_executor.py
nathaliafab Oct 30, 2025
dda0c87
Update nimrod/test_suite_generation/generators/codellama_test_suite_g…
nathaliafab Oct 30, 2025
5e6c389
Update nimrod/output_generation/semantic_conflicts_output_generator.py
nathaliafab Oct 30, 2025
3fb1a94
refactor: rename codellama to ollama
nathaliafab Nov 16, 2025
0c46b5f
docs: add docstrings for data loading and writing methods
nathaliafab Nov 16, 2025
4210022
Update nimrod/test_suite_generation/generators/ollama_test_suite_gene…
nathaliafab Nov 16, 2025
f6dc6bc
Update nimrod/test_suites_execution/test_suite_executor.py
nathaliafab Nov 16, 2025
cca64e7
fix(tests): update compile timeout and standardize assertion methods
nathaliafab Nov 16, 2025
5a05435
feat: add PromptManager class and prompt templates
nathaliafab Dec 18, 2025
5784994
fix: update paths for compilation and execution results to use report…
nathaliafab Dec 18, 2025
7f435b5
feat: enhance OllamaTestSuiteGenerator to support configurable model …
nathaliafab Dec 18, 2025
e99d5d0
feat: implement LLMOutputProcessor for output sanitization in OllamaT…
nathaliafab Dec 18, 2025
d7b02da
fix: update requirements.txt for specific tree_sitter versions and im…
nathaliafab Dec 18, 2025
bba8cf6
chore: update GitHub Actions workflow to use Python 3.10 and Java Tem…
nathaliafab Dec 18, 2025
ecbad7c
fix: update Maven setup action version and adjust env-config.json cre…
nathaliafab Dec 18, 2025
65bf193
fix: update env-config.json creation to use dynamic MAVEN_HOME path
nathaliafab Dec 18, 2025
74a3cc0
feat: add Docker support with Dockerfile and docker-compose.yml for c…
nathaliafab Dec 18, 2025
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
28 changes: 15 additions & 13 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,16 @@ jobs:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v2
- name: Set up Python 3.9
uses: actions/setup-python@v2
- uses: actions/checkout@v6
- name: Set up Python 3.10
uses: actions/setup-python@v6
with:
python-version: 3.9
python-version: '3.10'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install flake8 pytest mypy
if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
pip install flake8 pytest mypy
- name: Lint with flake8
run: |
# stop the build if there are Python syntax errors or undefined names
Expand All @@ -30,19 +30,21 @@ jobs:
- name: Lint with mypy
run: mypy nimrod/test_suite_generation/ nimrod/test_suites_execution/ nimrod/dynamic_analysis/ nimrod/core nimrod/output_generation nimrod/__main__.py nimrod/smat.py --ignore-missing-imports
- name: Setup Java
uses: actions/setup-java@v2
uses: actions/setup-java@v5
with:
distribution: 'adopt'
distribution: 'temurin'
java-version: '8'
- name: Setup Maven
uses: stCarolas/setup-maven@v4.1
uses: stCarolas/setup-maven@v5
with:
maven-version: 3.8.2
- name: Creating env-config.json
run: |
cd /home/runner/work/SMAT/SMAT/nimrod/tests/
java_path="/opt/hostedtoolcache/Java_Adopt_jdk/$(ls /opt/hostedtoolcache/Java_Adopt_jdk)/x64"
contents="$(jq --arg java_path "$java_path" '.java_home=$java_path | .maven_home = "/opt/hostedtoolcache/maven/3.5.4/x64"' env-config.json)"
repo_name=$(basename $GITHUB_REPOSITORY)
cd /home/runner/work/$repo_name/$repo_name/nimrod/tests/
contents="$(jq --arg java_path "$JAVA_HOME" --arg maven_path "${MAVEN_HOME:-/opt/hostedtoolcache/maven/3.8.2/x64}" '.java_home=$java_path | .maven_home=$maven_path' env-config.json)"
echo "${contents}" > env-config.json
cd /home/runner/work/SMAT/SMAT
cd /home/runner/work/$repo_name/$repo_name
- name: Test with pytest
run: |
pytest -k 'not test_general_behavior_study_semantic_conflict'
pytest -k 'not test_general_behavior_study_semantic_conflict' --color=yes
17 changes: 17 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
services:
smat:
build:
context: .
dockerfile: docker/Dockerfile
args:
USER_ID: ${USER_ID}
GROUP_ID: ${GROUP_ID}
image: smat-ubuntu
container_name: smat_container
volumes:
# Mount the current directory to /app in the container
- .:/app
# Mount the dataset directory to /data/dataset in the container (read-only)
- /path/to/your/mergedataset/:/data/dataset:ro
stdin_open: true
tty: true
44 changes: 44 additions & 0 deletions docker/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
FROM ubuntu:22.04

ENV DEBIAN_FRONTEND=noninteractive

ARG USER_ID
ARG GROUP_ID

# 1. Install system dependencies, Python, and Java in a single layer
RUN apt-get update && \
apt-get install -y --no-install-recommends \
python3.11 \
python3-pip \
python3.11-dev \
openjdk-8-jdk \
maven \
git \
curl \
jq \
build-essential \
ca-certificates && \

groupadd -g ${GROUP_ID} appuser && \
useradd -m -u ${USER_ID} -g ${GROUP_ID} appuser && \

update-alternatives --install /usr/bin/python3 python3 /usr/bin/python3.11 1 && \

apt-get clean && rm -rf /var/lib/apt/lists/*

# 2. Set Environment Variables for Java and Maven
ENV JAVA_HOME=/usr/lib/jvm/java-8-openjdk-amd64
ENV MAVEN_HOME=/usr/share/maven
ENV PATH="$MAVEN_HOME/bin:$JAVA_HOME/bin:$PATH"

# 3. Setup working directory and data mount point
WORKDIR /app
RUN mkdir -p /data/dataset && chown appuser:appuser /data/dataset

# 4. Install Python dependencies
COPY --chown=appuser:appuser requirements.txt .
RUN python3 -m pip install --no-cache-dir --upgrade pip && \
python3 -m pip install --no-cache-dir -r requirements.txt ruff mypy pytest

USER appuser
CMD ["/bin/bash", "-i"]
109 changes: 109 additions & 0 deletions docker/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
# Running SMAT with Docker Compose

This guide explains how to run SMAT in a containerized environment using **Docker Compose**. This ensures a consistent environment with Python 3.11, Java 8, and Maven, regardless of your host operating system.

## 1. Prerequisites

Install Docker on your system:

* **Docker Desktop** (Recommended for Windows and Mac): [Download here](https://docs.docker.com/desktop/)
* **Docker Engine** (For Linux): [Installation Guide](https://docs.docker.com/engine/install/)

---

## 2. Configuration

Before running the container, you need to configure three files to ensure the paths match the Docker environment.

### A. Docker Compose (Dataset Path)

Open `docker-compose.yml` and point the dataset volume to your local path:

```yaml
volumes:
- .:/app
# Replace the path below with the path to your dataset on your host machine
- /path/to/your/mergedataset/:/data/dataset:ro

```

*Note: The `:ro` flag ensures your dataset is read-only for safety.*

### B. SMAT Input Config (`input-smat.json`)

The `input-smat.json` should be in the **root directory** of the project, so that the container can see it. Internally, the scenario jars must point to the `/data/dataset/` path, for example:

```json
{
...
"scenarioJars": {
"base": "/data/dataset/antlr4/69ff2669eec265e25721dbc27cb00f6c381d0b41/...",
...
},
...
}
```

### C. Environment Config (`nimrod/tests/env-config.json`)

Point the `input_smat` path to the location inside the container. If it is on the root folder:

```json
"input_path": "/app/input-smat.json",
```

---

## 3. Running the Container

Navigate to the project root and run the following command according to your OS:

### Linux & macOS (Terminal)

The following command passes your user and group IDs to avoid permission issues with generated files:

```bash
USER_ID=$(id -u) GROUP_ID=$(id -g) docker compose run --rm --build smat
```

### Windows (PowerShell)

In PowerShell, the variables are handled differently:

```powershell
$env:USER_ID=1000; $env:GROUP_ID=1000; docker compose run --rm --build smat
```

*Note: On Windows, the default UID/GID 1000 is usually sufficient for Docker Desktop.*

---

## 4. Usage Inside the Container

Once the command finishes, you will be inside the Ubuntu shell at `/app`. You can run tests or start an analysis:

```bash
# Check if the dataset is visible
ls /data/dataset

# Run SMAT analysis
python3 -m nimrod

# Run tests
pytest -k 'not test_general_behavior_study_semantic_conflict'
```

### Command Breakdown:

* `run`: Starts a one-off container for interactive use.
* `--rm`: Automatically removes the container upon exit to keep your system clean.
* `--build`: Forces a rebuild of the image if you modified the Dockerfile or requirements.
* `smat`: The service name defined in `docker-compose.yml`.

---

## Troubleshooting

* **Dataset Not Found**: Ensure the path on the left side of the colon in `docker-compose.yml` is an absolute path to your local folder.
* **Permission Denied**: On Linux, double-check that `USER_ID` and `GROUP_ID` match the output of the `id` command on your host terminal.
* **File Changes**: Since we use volumes, any code change made on your host machine will be instantly reflected inside the container.
21 changes: 15 additions & 6 deletions nimrod/__main__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import logging
from typing import Dict, List
from typing import Dict, List, Any
from nimrod.dynamic_analysis.behavior_change_checker import BehaviorChangeChecker
from nimrod.dynamic_analysis.criteria.first_semantic_conflict_criteria import FirstSemanticConflictCriteria
from nimrod.dynamic_analysis.criteria.second_semantic_conflict_criteria import SecondSemanticConflictCriteria
Expand All @@ -16,6 +16,7 @@
from nimrod.test_suite_generation.generators.randoop_test_suite_generator import RandoopTestSuiteGenerator
from nimrod.test_suite_generation.generators.evosuite_differential_test_suite_generator import EvosuiteDifferentialTestSuiteGenerator
from nimrod.test_suite_generation.generators.evosuite_test_suite_generator import EvosuiteTestSuiteGenerator
from nimrod.test_suite_generation.generators.ollama_test_suite_generator import OllamaTestSuiteGenerator
from nimrod.test_suite_generation.generators.project_test_suite_generator import ProjectTestSuiteGenerator
from nimrod.test_suites_execution.main import TestSuitesExecution, TestSuiteExecutor
from nimrod.tools.bin import MOD_RANDOOP, RANDOOP
Expand All @@ -24,9 +25,9 @@
from nimrod.input_parsing.input_parser import CsvInputParser, JsonInputParser


def get_test_suite_generators(config: Dict[str, str]) -> List[TestSuiteGenerator]:
def get_test_suite_generators(config: Dict[str, Any]) -> List[TestSuiteGenerator]:
config_generators = config.get(
'test_suite_generators', ['randoop', 'randoop-modified', 'evosuite', 'evosuite-differential', 'project'])
'test_suite_generators', ['randoop', 'randoop-modified', 'evosuite', 'evosuite-differential', 'ollama', 'project'])
generators: List[TestSuiteGenerator] = list()

if 'randoop' in config_generators:
Expand All @@ -38,13 +39,21 @@ def get_test_suite_generators(config: Dict[str, str]) -> List[TestSuiteGenerator
generators.append(EvosuiteTestSuiteGenerator(Java()))
if 'evosuite-differential' in config_generators:
generators.append(EvosuiteDifferentialTestSuiteGenerator(Java()))
if 'ollama' in config_generators:
# Create one generator instance for each configured model
api_params = config.get('api_params', {})
if api_params:
for model_key, model_config in api_params.items():
generators.append(OllamaTestSuiteGenerator(Java(), model_key, model_config))
else:
generators.append(OllamaTestSuiteGenerator(Java()))
if 'project' in config_generators:
generators.append(ProjectTestSuiteGenerator(Java()))

return generators


def get_output_generators(config: Dict[str, str]) -> List[OutputGenerator]:
def get_output_generators(config: Dict[str, Any]) -> List[OutputGenerator]:
config_generators = config.get(
'output_generators', ['behavior_changes', 'semantic_conflicts', 'test_suites'])
generators: List[OutputGenerator] = list()
Expand All @@ -60,7 +69,7 @@ def get_output_generators(config: Dict[str, str]) -> List[OutputGenerator]:
return generators


def parse_scenarios_from_input(config: Dict[str, str]) -> List[MergeScenarioUnderAnalysis]:
def parse_scenarios_from_input(config: Dict[str, Any]) -> List[MergeScenarioUnderAnalysis]:
json_input = config.get('input_path', "")
csv_input_path = config.get('path_hash_csv', "")

Expand Down Expand Up @@ -95,7 +104,7 @@ def main():
if scenario.run_analysis:
smat.run_tool_for_semmantic_conflict_detection(scenario)
else:
logging.info(f"Skipping tool execution for project f{scenario.project_name}")
logging.info(f"Skipping tool execution for project {scenario.project_name}")


if __name__ == '__main__':
Expand Down
4 changes: 2 additions & 2 deletions nimrod/core/merge_scenario_under_analysis.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
from typing import List, Dict
from typing import List, Dict, Union


class MergeScenarioUnderAnalysis:
def __init__(self, project_name: str, run_analysis: bool, scenario_commits: "ScenarioInformation", targets: "Dict[str, List[str]]", scenario_jars: "ScenarioInformation", jar_type: str):
def __init__(self, project_name: str, run_analysis: bool, scenario_commits: "ScenarioInformation", targets: "Dict[str, Union[List[Dict[str, str]], List[str]]]", scenario_jars: "ScenarioInformation", jar_type: str):
self.project_name = project_name
self.run_analysis = run_analysis
self.scenario_commits = scenario_commits
Expand Down
29 changes: 25 additions & 4 deletions nimrod/output_generation/output_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@


class OutputGenerator(ABC, Generic[T]):
REPORTS_DIRECTORY = path.join(get_base_output_path(), "reports")
parent_dir = path.dirname(get_base_output_path())
REPORTS_DIRECTORY = path.join(parent_dir, "reports")

def __init__(self, report_name: str) -> None:
super().__init__()
Expand All @@ -27,9 +28,29 @@ def write_report(self, context: OutputGeneratorContext) -> None:
file_path = path.join(self.REPORTS_DIRECTORY, self._report_name)

logging.info(f"Starting data processing of {self._report_name} report")
data = self._generate_report_data(context)
new_data = self._generate_report_data(context)
logging.info(f"Finished data processing of {self._report_name} report")

with open(file_path, "w") as write:
json.dump(data, write)
existing_data = self._load_existing_data(file_path)

if not isinstance(existing_data, list):
existing_data = [existing_data] if existing_data else []
existing_data.append(new_data)

self._write_json(file_path, existing_data)
logging.info(f"Finished generation of {self._report_name} report")

def _load_existing_data(self, file_path: str):
"""Loads data stored from previous runs so new data is appended instead of overwriting."""
if not path.exists(file_path):
return []
try:
with open(file_path, "r") as read_file:
return json.load(read_file)
except json.JSONDecodeError:
return []

def _write_json(self, file_path: str, data) -> None:
"""Writes data to a JSON file with indentation for readability."""
with open(file_path, "w") as write_file:
json.dump(data, write_file, indent=4)
Loading