Skip to content
Open
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
42 changes: 42 additions & 0 deletions .github/workflows/ci-build.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,48 @@ jobs:
- name: Dynawo and powsybl tests
run: pytest tests/dynamic tests/powsybl -v

windows-pytests:
runs-on: windows-latest
timeout-minutes: 60
defaults:
run:
shell: bash
steps:
- uses: actions/checkout@v7

- name: Install Julia 1.12
uses: julia-actions/setup-julia@v3
with:
version: '1.12'

- name: Cache Julia depot
uses: actions/cache@v6
with:
path: ~/.julia
key: julia-depot-${{ runner.os }}-julia1.12-${{ hashFiles('gridfm_datakit/juliapkg.json') }}
restore-keys: |
julia-depot-${{ runner.os }}-julia1.12-

- name: Set up Python 3.12
uses: actions/setup-python@v7
with:
python-version: '3.12'
cache: pip
cache-dependency-path: pyproject.toml

- name: Install Python dependencies
run: |
python -m pip install --upgrade pip wheel
pip install -e ".[test]"

- name: Set up Julia packages (PowerModels)
run: python -c "from juliacall import Main as jl; jl.seval('using PowerModels, Ipopt, Memento')"

- name: Unit tests
run: |
export SKIP_LARGE_GRIDS=1
pytest tests/ -v --durations=25

codeql:
name: CodeQL (Python)
runs-on: ubuntu-latest
Expand Down
14 changes: 12 additions & 2 deletions gridfm_datakit/network.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,16 @@
from gridfm_datakit.utils.idx_gen import GEN_BUS, GEN_STATUS, PG, QG


def _julia_string(value: str) -> str:
"""Return ``value`` as a Julia string literal.

Backslashes, quotes and ``$`` are escaped, so Windows paths and paths
containing interpolation characters reach Julia unchanged.
"""
escaped = value.replace("\\", "\\\\").replace('"', '\\"').replace("$", "\\$")
return f'"{escaped}"'


def correct_network(network_path: str, force: bool = False) -> str:
"""
Load a MATPOWER network using PowerModels via run_julia
Expand Down Expand Up @@ -101,8 +111,8 @@ def correct_network(network_path: str, force: bool = False) -> str:
julia_code = [
"using PowerModels",
"PowerModels.silence()",
f'data = PowerModels.parse_file("{network_path}")',
f'PowerModels.export_matpower("{tmp_path}", data)',
f"data = PowerModels.parse_file({_julia_string(network_path)})",
f"PowerModels.export_matpower({_julia_string(tmp_path)}, data)",
]

# Run Julia
Expand Down
4 changes: 3 additions & 1 deletion gridfm_datakit/process/solver_output.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,9 @@
from typing import Dict, Iterator, Optional

try:
_LIBC: Optional[ctypes.CDLL] = ctypes.CDLL(None)
_LIBC: Optional[ctypes.CDLL] = ctypes.CDLL(
"ucrtbase" if sys.platform == "win32" else None,
)
except Exception: # pragma: no cover - platform without a loadable libc
_LIBC = None

Expand Down
41 changes: 41 additions & 0 deletions tests/test_correct_network.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
"""
Test cases for passing network file paths to Julia in correct_network.
"""

from pathlib import Path

import pytest

from gridfm_datakit.network import (
_julia_string,
correct_network,
get_pglib_source_path,
)


@pytest.mark.parametrize(
"value",
[
"/plain/case14.m",
"C:\\Users\\runner\\AppData\\Local\\case14.m",
'dir "quoted"/case14.m',
"dir/$HOME/case14.m",
],
)
def test_julia_string_round_trips(value):
from juliacall import Main as jl

assert jl.seval(_julia_string(value)) == value


def test_correct_network_handles_special_characters_in_path(tmp_path):
directory = tmp_path / "grid $HOME dir"
directory.mkdir()
source = directory / "case14_ieee.m"
with open(get_pglib_source_path("case14_ieee"), encoding="utf-8") as f:
source.write_text(f.read(), encoding="utf-8")

corrected = correct_network(str(source))

assert corrected == str(directory / "case14_ieee_corrected.m")
assert Path(corrected).stat().st_size > 0
3 changes: 2 additions & 1 deletion tests/test_solver_output.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

import ctypes
import os
import sys

import pytest

Expand Down Expand Up @@ -88,7 +89,7 @@ class TestRedirectCStdio:
# capturing fd-level output that bypasses Python's sys.stdout.

def test_redirects_c_level_writes_to_file(self, tmp_path, capfd):
libc = ctypes.CDLL(None)
libc = ctypes.CDLL("ucrtbase" if sys.platform == "win32" else None)
log = tmp_path / "out.log"

with redirect_fds(str(log)):
Expand Down
Loading