From fcfb67be868251de5d27bf768a9b1bb6c43b94ea Mon Sep 17 00:00:00 2001 From: RemDelaporteMathurin Date: Tue, 29 Jul 2025 21:19:33 -0400 Subject: [PATCH 01/15] added pytest to requirements --- requirements.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 39c99271..d00bc666 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,4 +9,5 @@ Werkzeug==3.1.3 pathsim==0.7.0 matplotlib==3.7.0 numpy==1.24.0 -plotly~=6 \ No newline at end of file +plotly~=6 +pytest \ No newline at end of file From 63748874408f227850418fe42ea170e6aa8f87aa Mon Sep 17 00:00:00 2001 From: RemDelaporteMathurin Date: Tue, 29 Jul 2025 21:31:44 -0400 Subject: [PATCH 02/15] run backend as package instead of directly --- package.json | 2 +- src/__init__.py | 2 ++ src/backend.py | 4 ++-- test/test_backend.py | 5 +++++ 4 files changed, 10 insertions(+), 3 deletions(-) create mode 100644 src/__init__.py create mode 100644 test/test_backend.py diff --git a/package.json b/package.json index e4f013cb..b16423e9 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ "build": "vite build", "lint": "eslint .", "preview": "vite preview", - "start:backend": "python ./src/backend.py", + "start:backend": "python -m src.backend", "start:both": "concurrently \"npm run dev\" \"npm run start:backend\"" }, "dependencies": { diff --git a/src/__init__.py b/src/__init__.py new file mode 100644 index 00000000..dd4de8c1 --- /dev/null +++ b/src/__init__.py @@ -0,0 +1,2 @@ +from . import custom_pathsim_blocks +from . import convert_to_python diff --git a/src/backend.py b/src/backend.py index 0ce1e2f8..8f94b01b 100644 --- a/src/backend.py +++ b/src/backend.py @@ -2,7 +2,6 @@ import json from flask import Flask, request, jsonify from flask_cors import CORS -from convert_to_python import convert_graph_to_python import math import numpy as np import plotly.graph_objects as go @@ -28,7 +27,8 @@ PID, Schedule, ) -from custom_pathsim_blocks import Process, Splitter +from .custom_pathsim_blocks import Process, Splitter +from .convert_to_python import convert_graph_to_python NAME_TO_SOLVER = { "SSPRK22": pathsim.solvers.SSPRK22, diff --git a/test/test_backend.py b/test/test_backend.py new file mode 100644 index 00000000..f9f60d29 --- /dev/null +++ b/test/test_backend.py @@ -0,0 +1,5 @@ +from src.backend import make_blocks + + +def test(): + pass From e7f4c919e382b889f5688b02790a89b4debaf667 Mon Sep 17 00:00:00 2001 From: RemDelaporteMathurin Date: Tue, 29 Jul 2025 21:34:06 -0400 Subject: [PATCH 03/15] added workflow --- .github/workflows/ci.yml | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..e8a9d060 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,31 @@ +name: Python application + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +permissions: + contents: read + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + - name: Set up Python 3.10 + uses: actions/setup-python@v3 + with: + python-version: "3.10" + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install pytest + if [ -f requirements.txt ]; then pip install -r requirements.txt; fi + + - name: Test with pytest + run: | + pytest \ No newline at end of file From b2d1a9d212ff832891417a778d2a39be0a800903 Mon Sep 17 00:00:00 2001 From: RemDelaporteMathurin Date: Tue, 29 Jul 2025 21:37:59 -0400 Subject: [PATCH 04/15] fixed requirement --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index d00bc666..d9e63044 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,5 +9,5 @@ Werkzeug==3.1.3 pathsim==0.7.0 matplotlib==3.7.0 numpy==1.24.0 -plotly~=6 +plotly~=6.0 pytest \ No newline at end of file From fd7f717fa2a5fbe75e5dcf9c9097b56bf949f886 Mon Sep 17 00:00:00 2001 From: RemDelaporteMathurin Date: Tue, 29 Jul 2025 21:41:45 -0400 Subject: [PATCH 05/15] added backend to init --- src/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/__init__.py b/src/__init__.py index dd4de8c1..8309909e 100644 --- a/src/__init__.py +++ b/src/__init__.py @@ -1,2 +1,3 @@ from . import custom_pathsim_blocks from . import convert_to_python +from . import backend From 6d6d9f5a6cc0c1155164f3d4ec0832af6db21a59 Mon Sep 17 00:00:00 2001 From: RemDelaporteMathurin Date: Tue, 29 Jul 2025 21:44:07 -0400 Subject: [PATCH 06/15] explicit python command --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e8a9d060..7b46268e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -28,4 +28,4 @@ jobs: - name: Test with pytest run: | - pytest \ No newline at end of file + python -m pytest test \ No newline at end of file From 591308c9811f92302f05f689440943a6f0afe515 Mon Sep 17 00:00:00 2001 From: RemDelaporteMathurin Date: Tue, 29 Jul 2025 21:45:00 -0400 Subject: [PATCH 07/15] init in test --- test/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 test/__init__.py diff --git a/test/__init__.py b/test/__init__.py new file mode 100644 index 00000000..e69de29b From 99ba749bff45f72d80149f2a5be923a2c8339b3a Mon Sep 17 00:00:00 2001 From: RemDelaporteMathurin Date: Tue, 29 Jul 2025 21:50:58 -0400 Subject: [PATCH 08/15] long shot --- .github/workflows/ci.yml | 197 ++++++++++++++++++++++++++++++++++++--- src/backend.py | 9 ++ 2 files changed, 193 insertions(+), 13 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7b46268e..bf06b915 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,31 +1,202 @@ -name: Python application +name: CI - Build and Test Application on: push: - branches: [ "main" ] + branches: [ main, develop ] pull_request: - branches: [ "main" ] + branches: [ main, develop ] permissions: contents: read jobs: - build: - + test: runs-on: ubuntu-latest + + strategy: + matrix: + node-version: [20.x] + python-version: ['3.10', '3.11'] steps: - - uses: actions/checkout@v4 - - name: Set up Python 3.10 - uses: actions/setup-python@v3 + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node-version }} + cache: 'npm' + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v4 with: - python-version: "3.10" + python-version: ${{ matrix.python-version }} + + - name: Cache Python dependencies + uses: actions/cache@v3 + with: + path: ~/.cache/pip + key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }} + restore-keys: | + ${{ runner.os }}-pip- + + - name: Install Python dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + + - name: Install Node.js dependencies + run: npm ci + + - name: Lint frontend code + run: npm run lint + + - name: Build frontend + run: npm run build + + - name: Run Python tests + run: | + python -m pytest test/ -v + + - name: Start backend server and health check + run: | + # Start the backend server in the background + python -m src.backend & + BACKEND_PID=$! + + # Wait for the server to start + echo "Waiting for backend server to start..." + sleep 10 + + # Health check - test if the server is responding + max_attempts=30 + attempt=1 + + while [ $attempt -le $max_attempts ]; do + if curl -f http://localhost:8000/health 2>/dev/null; then + echo "Backend server is healthy!" + break + elif curl -f http://localhost:8000/ 2>/dev/null; then + echo "Backend server is responding!" + break + else + echo "Attempt $attempt/$max_attempts: Backend not ready yet..." + sleep 2 + attempt=$((attempt + 1)) + fi + done + + if [ $attempt -gt $max_attempts ]; then + echo "Backend server failed to start properly" + kill $BACKEND_PID 2>/dev/null || true + exit 1 + fi + + # Test basic API endpoints if they exist + echo "Testing backend endpoints..." + + # Clean up + kill $BACKEND_PID 2>/dev/null || true + echo "Backend server test completed successfully!" + + - name: Test frontend build serves correctly + run: | + # Start the preview server in the background + npm run preview & + FRONTEND_PID=$! + + # Wait for the server to start + echo "Waiting for frontend server to start..." + sleep 5 + + # Health check for frontend + max_attempts=15 + attempt=1 + + while [ $attempt -le $max_attempts ]; do + if curl -f http://localhost:4173/ 2>/dev/null; then + echo "Frontend server is serving correctly!" + break + else + echo "Attempt $attempt/$max_attempts: Frontend not ready yet..." + sleep 2 + attempt=$((attempt + 1)) + fi + done + + if [ $attempt -gt $max_attempts ]; then + echo "Frontend server failed to start properly" + kill $FRONTEND_PID 2>/dev/null || true + exit 1 + fi + + # Clean up + kill $FRONTEND_PID 2>/dev/null || true + echo "Frontend server test completed successfully!" + + integration-test: + runs-on: ubuntu-latest + needs: test + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: '20.x' + cache: 'npm' + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: '3.11' + - name: Install dependencies run: | python -m pip install --upgrade pip - pip install pytest - if [ -f requirements.txt ]; then pip install -r requirements.txt; fi + pip install -r requirements.txt + npm ci + + - name: Build frontend + run: npm run build - - name: Test with pytest + - name: Test full application stack run: | - python -m pytest test \ No newline at end of file + # Start both frontend and backend + echo "Starting full application stack..." + + # Start backend + python -m src.backend & + BACKEND_PID=$! + + # Start frontend preview + npm run preview & + FRONTEND_PID=$! + + # Wait for both services + sleep 15 + + # Test both services are running + echo "Testing backend..." + if ! curl -f http://localhost:8000/ 2>/dev/null; then + echo "Backend failed to start" + kill $BACKEND_PID $FRONTEND_PID 2>/dev/null || true + exit 1 + fi + + echo "Testing frontend..." + if ! curl -f http://localhost:4173/ 2>/dev/null; then + echo "Frontend failed to start" + kill $BACKEND_PID $FRONTEND_PID 2>/dev/null || true + exit 1 + fi + + echo "Both services are running successfully!" + + # Clean up + kill $BACKEND_PID $FRONTEND_PID 2>/dev/null || true + + echo "Integration test completed successfully!" \ No newline at end of file diff --git a/src/backend.py b/src/backend.py index 8f94b01b..15b80aa4 100644 --- a/src/backend.py +++ b/src/backend.py @@ -52,6 +52,15 @@ os.makedirs(SAVE_DIR, exist_ok=True) +# Health check endpoint for CI/CD +@app.route("/", methods=["GET"]) +@app.route("/health", methods=["GET"]) +def health_check(): + return jsonify( + {"status": "healthy", "message": "Fuel Cycle Simulator Backend is running"} + ), 200 + + # Function to save graphs @app.route("/save", methods=["POST"]) def save_graph(): From a2b68d82febe9e48258092b1b44902237c77d046 Mon Sep 17 00:00:00 2001 From: RemDelaporteMathurin Date: Tue, 29 Jul 2025 21:52:37 -0400 Subject: [PATCH 09/15] skip linting --- .github/workflows/ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bf06b915..382d3521 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -51,6 +51,7 @@ jobs: - name: Lint frontend code run: npm run lint + stop-on-error: false - name: Build frontend run: npm run build From 376dc8447bb7df93ea05eabca22b7f1a20e2daec Mon Sep 17 00:00:00 2001 From: RemDelaporteMathurin Date: Tue, 29 Jul 2025 21:54:22 -0400 Subject: [PATCH 10/15] continue on error --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 382d3521..d83cfa20 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -51,7 +51,7 @@ jobs: - name: Lint frontend code run: npm run lint - stop-on-error: false + continue-on-error: true - name: Build frontend run: npm run build From c7634442bf5d8b571524d69256b990d3dd82c71e Mon Sep 17 00:00:00 2001 From: RemDelaporteMathurin Date: Wed, 30 Jul 2025 08:53:29 -0400 Subject: [PATCH 11/15] added auto_block_construction --- src/backend.py | 51 +++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 40 insertions(+), 11 deletions(-) diff --git a/src/backend.py b/src/backend.py index 15b80aa4..b3edcbdf 100644 --- a/src/backend.py +++ b/src/backend.py @@ -415,6 +415,45 @@ def make_solver_params(solver_prms, eval_namespace=None): } +def auto_block_construction(node: dict, eval_namespace: dict = None) -> Block: + """ + Automatically constructs a block object from a node dictionary. + + Args: + node: The node dictionary containing block information. + eval_namespace: A namespace for evaluating expressions. Defaults to None. + + Raises: + ValueError: If the block type is unknown or if there are issues with evaluation. + + Returns: + The constructed block object. + """ + if eval_namespace is None: + eval_namespace = globals() + + block_type = node["type"] + + if eval_namespace is None: + eval_namespace = globals() + + block_type = node["type"] + if block_type not in map_str_to_object: + raise ValueError(f"Unknown block type: {block_type}") + + block_class = map_str_to_object[block_type] + + # skip 'self' + parameters_for_class = block_class.__init__.__code__.co_varnames[1:] + + parameters = { + k: eval(v, eval_namespace) + for k, v in node["data"].items() + if k in parameters_for_class + } + return block_class(**parameters) + + def make_blocks(nodes, edges, eval_namespace=None): blocks, events = [], [] @@ -454,17 +493,7 @@ def make_blocks(nodes, edges, eval_namespace=None): ], ) else: # try automated construction - block_class = map_str_to_object[block_type] - - # skip 'self' - parameters_for_class = block_class.__init__.__code__.co_varnames[1:] - - parameters = { - k: eval(v, eval_namespace) - for k, v in node["data"].items() - if k in parameters_for_class - } - block = block_class(**parameters) + block = auto_block_construction(node, eval_namespace) block.id = node["id"] block.label = node["data"]["label"] From 1c19b80e19b29dc473b0cd173d91695450498740 Mon Sep 17 00:00:00 2001 From: RemDelaporteMathurin Date: Wed, 30 Jul 2025 08:57:15 -0400 Subject: [PATCH 12/15] typing --- src/backend.py | 49 ++++++++++++++++++++++++++----------------------- 1 file changed, 26 insertions(+), 23 deletions(-) diff --git a/src/backend.py b/src/backend.py index b3edcbdf..1b58970a 100644 --- a/src/backend.py +++ b/src/backend.py @@ -10,6 +10,7 @@ import json as plotly_json from pathsim import Simulation, Connection +from pathsim.events import Event import pathsim.solvers from pathsim.blocks import ( Scope, @@ -36,6 +37,28 @@ "RKF21": pathsim.solvers.RKF21, } +map_str_to_object = { + "constant": Constant, + "stepsource": StepSource, + "pulsesource": PulseSource, + "amplifier": Amplifier, + "amplifier_reverse": Amplifier, + "scope": Scope, + "splitter2": Splitter, + "splitter3": Splitter, + "adder": Adder, + "adder_reverse": Adder, + "multiplier": Multiplier, + "process": Process, + "process_horizontal": Process, + "rng": RNG, + "pid": PID, + "integrator": Integrator, + "function": Function, + "delay": Delay, +} + + # app = Flask(__name__) # CORS(app, supports_credentials=True) @@ -393,28 +416,6 @@ def make_solver_params(solver_prms, eval_namespace=None): return solver_prms, extra_params, duration -map_str_to_object = { - "constant": Constant, - "stepsource": StepSource, - "pulsesource": PulseSource, - "amplifier": Amplifier, - "amplifier_reverse": Amplifier, - "scope": Scope, - "splitter2": Splitter, - "splitter3": Splitter, - "adder": Adder, - "adder_reverse": Adder, - "multiplier": Multiplier, - "process": Process, - "process_horizontal": Process, - "rng": RNG, - "pid": PID, - "integrator": Integrator, - "function": Function, - "delay": Delay, -} - - def auto_block_construction(node: dict, eval_namespace: dict = None) -> Block: """ Automatically constructs a block object from a node dictionary. @@ -454,7 +455,9 @@ def auto_block_construction(node: dict, eval_namespace: dict = None) -> Block: return block_class(**parameters) -def make_blocks(nodes, edges, eval_namespace=None): +def make_blocks( + nodes: list[dict], edges: list[dict], eval_namespace: dict = None +) -> tuple[list[Block], list[Event]]: blocks, events = [], [] for node in nodes: From a0ad14d725327851faa1eec7762bd2d88b0ecb0a Mon Sep 17 00:00:00 2001 From: RemDelaporteMathurin Date: Wed, 30 Jul 2025 08:57:20 -0400 Subject: [PATCH 13/15] added tests --- test/test_backend.py | 146 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 143 insertions(+), 3 deletions(-) diff --git a/test/test_backend.py b/test/test_backend.py index f9f60d29..cf4d995a 100644 --- a/test/test_backend.py +++ b/test/test_backend.py @@ -1,5 +1,145 @@ -from src.backend import make_blocks +from src.backend import create_integrator, auto_block_construction +from src.custom_pathsim_blocks import Process, Splitter +import pathsim.blocks -def test(): - pass +import pytest + + +# Node templates with constructor parameters + label for each block type +NODE_TEMPLATES = { + "constant": {"type": "constant", "data": {"value": "1.0", "label": "Constant"}}, + "stepsource": { + "type": "stepsource", + "data": {"amplitude": "1.0", "tau": "1.0", "label": "Step Source"}, + }, + "pulsesource": { + "type": "pulsesource", + "data": {"amplitude": "1.0", "tau": "1.0", "label": "Pulse Source"}, + }, + "amplifier": {"type": "amplifier", "data": {"gain": "2.0", "label": "Amplifier"}}, + "adder": {"type": "adder", "data": {"label": "Adder"}}, + "multiplier": {"type": "multiplier", "data": {"label": "Multiplier"}}, + "integrator": { + "type": "integrator", + "data": {"initial_value": "0.0", "label": "Integrator", "reset_times": ""}, + }, + "function": {"type": "function", "data": {"func": "x[0]", "label": "Function"}}, + "delay": {"type": "delay", "data": {"tau": "1.0", "label": "Delay"}}, + "rng": {"type": "rng", "data": {"seed": "42", "label": "RNG"}}, + "pid": { + "type": "pid", + "data": {"kp": "1.0", "ki": "0.0", "kd": "0.0", "label": "PID"}, + }, + "process": { + "type": "process", + "data": { + "residence_time": "1.0", + "ic": "0.0", + "gen": "0.0", + "label": "Process", + }, + }, + "splitter2": { + "type": "splitter2", + "data": {"f1": "0.5", "f2": "0.5", "label": "Splitter 2"}, + }, + "splitter3": { + "type": "splitter3", + "data": {"f1": "1/3", "f2": "1/3", "f3": "1/3", "label": "Splitter 3"}, + }, + "scope": {"type": "scope", "data": {"label": "Scope"}}, +} + + +@pytest.fixture +def node_factory(): + """ + Factory fixture that creates node dictionaries for different pathsim block types. + + Usage: + def test_something(node_factory): + integrator_node = node_factory("integrator", id="test_1") + constant_node = node_factory("constant", id="test_2", data_overrides={"value": "5.0"}) + """ + + def _create_node(block_type: str, id: str = "1", data_overrides: dict = None): + if block_type not in NODE_TEMPLATES: + available_types = list(NODE_TEMPLATES.keys()) + raise ValueError( + f"Unknown block type: {block_type}. Available types: {available_types}" + ) + + # Start with template + node = { + "id": id, + "type": block_type, + "data": NODE_TEMPLATES[block_type]["data"].copy(), + } + + # Apply any data overrides + if data_overrides: + node["data"].update(data_overrides) + + return node + + return _create_node + + +def test_create_integrator(): + node = { + "data": {"initial_value": "", "label": "IV vial 1", "reset_times": ""}, + "id": "9", + "type": "integrator", + } + integrator, events = create_integrator(node) + + assert isinstance(integrator, pathsim.blocks.Integrator) + assert integrator.initial_value == 0 + for event in events: + assert isinstance(event, pathsim.blocks.Schedule) + + +@pytest.mark.parametrize( + "block_type,expected_class", + [ + ("constant", pathsim.blocks.Constant), + ("amplifier", pathsim.blocks.Amplifier), + ("adder", pathsim.blocks.Adder), + ("multiplier", pathsim.blocks.Multiplier), + ("rng", pathsim.blocks.RNG), + ("pid", pathsim.blocks.PID), + ("process", Process), + ("splitter2", Splitter), + ("splitter3", Splitter), + ], +) +def test_auto_block_construction(node_factory, block_type, expected_class): + node = node_factory(block_type) + block = auto_block_construction(node) + assert isinstance(block, expected_class) + + +@pytest.mark.parametrize( + "block_type,expected_class", + [ + ("constant", pathsim.blocks.Constant), + ("amplifier", pathsim.blocks.Amplifier), + ("adder", pathsim.blocks.Adder), + ("multiplier", pathsim.blocks.Multiplier), + ("rng", pathsim.blocks.RNG), + ("pid", pathsim.blocks.PID), + ("process", Process), + ("splitter2", Splitter), + ("splitter3", Splitter), + ], +) +def test_auto_block_construction_with_var(node_factory, block_type, expected_class): + node = node_factory(block_type) + # replace one data value with "2*var1" + for k, v in node["data"].items(): + if k != "label": + node["data"][k] = "2*var1" + break + block = auto_block_construction(node, eval_namespace={"var1": 5.5}) + assert isinstance(block, expected_class) From 40f9ff015e57510b9fcf3ca81b03997d152c4353 Mon Sep 17 00:00:00 2001 From: RemDelaporteMathurin Date: Wed, 30 Jul 2025 09:03:38 -0400 Subject: [PATCH 14/15] test_create_function --- test/test_backend.py | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/test/test_backend.py b/test/test_backend.py index cf4d995a..016507a0 100644 --- a/test/test_backend.py +++ b/test/test_backend.py @@ -1,4 +1,4 @@ -from src.backend import create_integrator, auto_block_construction +from src.backend import create_integrator, auto_block_construction, create_function from src.custom_pathsim_blocks import Process, Splitter import pathsim.blocks @@ -24,7 +24,10 @@ "type": "integrator", "data": {"initial_value": "0.0", "label": "Integrator", "reset_times": ""}, }, - "function": {"type": "function", "data": {"func": "x[0]", "label": "Function"}}, + "function": { + "type": "function", + "data": {"expression": "3*x**2", "label": "Function"}, + }, "delay": {"type": "delay", "data": {"tau": "1.0", "label": "Delay"}}, "rng": {"type": "rng", "data": {"seed": "42", "label": "RNG"}}, "pid": { @@ -143,3 +146,14 @@ def test_auto_block_construction_with_var(node_factory, block_type, expected_cla break block = auto_block_construction(node, eval_namespace={"var1": 5.5}) assert isinstance(block, expected_class) + + +def test_create_function(): + node = { + "data": {"expression": "3*x**2 + b", "label": "Function"}, + "id": "10", + "type": "function", + } + block = create_function(node, eval_namespace={"b": 2.5}) + assert isinstance(block, pathsim.blocks.Function) + assert block.func(2) == 3 * 2**2 + 2.5 From 408e899acae4ca1b43596cdc83e5a3de24bc0949 Mon Sep 17 00:00:00 2001 From: RemDelaporteMathurin Date: Wed, 30 Jul 2025 12:22:13 -0400 Subject: [PATCH 15/15] suggestions from code review --- .github/workflows/ci.yml | 4 ++-- test/test_backend.py | 6 ++++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d83cfa20..fb000c69 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,9 +2,9 @@ name: CI - Build and Test Application on: push: - branches: [ main, develop ] + branches: [ main ] pull_request: - branches: [ main, develop ] + branches: [ main ] permissions: contents: read diff --git a/test/test_backend.py b/test/test_backend.py index 016507a0..795ae478 100644 --- a/test/test_backend.py +++ b/test/test_backend.py @@ -118,6 +118,9 @@ def test_create_integrator(): ], ) def test_auto_block_construction(node_factory, block_type, expected_class): + """Test auto_block_construction for various block types. + Using the node_factory fixture to create nodes dynamically. + """ node = node_factory(block_type) block = auto_block_construction(node) assert isinstance(block, expected_class) @@ -138,6 +141,9 @@ def test_auto_block_construction(node_factory, block_type, expected_class): ], ) def test_auto_block_construction_with_var(node_factory, block_type, expected_class): + """ + Test auto_block_construction with a variable in the data. + This simulates a case where a data value is replaced with a variable expression.""" node = node_factory(block_type) # replace one data value with "2*var1" for k, v in node["data"].items():