Skip to content

Conversation

@calvinp0
Copy link
Member

Adds reporting of test durations to the CI output, which helps in identifying slow tests and optimizing performance.

This is achieved by:

  • Setting the PYTEST_INLINE_DURATIONS environment variable in the CI workflow to enable duration reporting.
  • Introducing a conftest.py file that hooks into the pytest reporting mechanism to print the duration of each test.

Copilot AI review requested due to automatic review settings January 26, 2026 19:32
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds inline per-test duration reporting to CI logs to help identify slow tests.

Changes:

  • Add a conftest.py pytest hook that prints each test’s runtime when PYTEST_INLINE_DURATIONS=1.
  • Export PYTEST_INLINE_DURATIONS=1 in CI workflow steps running unit and functional tests.
  • Introduce caching/regex refactors in arc/family/family.py (note: appears unrelated to CI duration reporting).

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.

File Description
conftest.py Adds a pytest reporting hook to print per-test durations when enabled via env var.
arc/family/family.py Adds global regex compilation and caches for parsed entries / reaction families.
.github/workflows/ci.yml Enables duration printing in CI by exporting PYTEST_INLINE_DURATIONS=1 for pytest runs.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 839 to 842
signature = (len(groups_as_lines), groups_as_lines[0] if groups_as_lines else '', groups_as_lines[-1] if groups_as_lines else '')
cache_key = id(groups_as_lines)
cached = _ENTRIES_CACHE.get(cache_key)
if cached is not None and cached[0] == signature:
Copy link

Copilot AI Jan 26, 2026

Choose a reason for hiding this comment

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

The new _ENTRIES_CACHE is keyed by id(groups_as_lines) with a very weak signature (len + first/last line). id() values can be reused after GC, and different lists can easily share this signature, which can return a wrong cached entries_map. Also, the cache can grow unbounded since entries are never evicted. Consider using a deterministic cache key derived from the full content (e.g., hash of ''.join(groups_as_lines)), and/or an LRU/size-bounded cache, or key the cache by a stable identifier (e.g., family label / groups file path) instead of id().

Copilot uses AI. Check for mistakes.
Comment on lines 25 to 33
_ENTRY_RE = re.compile(r'entry\((.*?)\)', re.DOTALL)
_ENTRY_LABEL_RE = re.compile(r'label = "(.*?)"')
_ENTRY_GROUP_RE = re.compile(r'group =(.*?)(?=\w+ =)', re.DOTALL)
_ENTRIES_CACHE: Dict[int, Tuple[Tuple[int, str, str], Dict[str, str]]] = {}
_REACTION_FAMILY_CACHE: Dict[Tuple[str, bool], 'ReactionFamily'] = {}

Copy link

Copilot AI Jan 26, 2026

Choose a reason for hiding this comment

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

This PR is described as adding pytest duration reporting in CI, but this file introduces additional behavior changes (regex precompilation plus global caches and get_reaction_family() usage) that appear unrelated to CI reporting. If these are intentional, they should be called out in the PR description; otherwise, consider splitting them into a separate PR to keep scope focused and reviewable.

Copilot uses AI. Check for mistakes.
Comment on lines +861 to +882
key = (label, consider_arc_families)
cached = _REACTION_FAMILY_CACHE.get(key)
if cached is not None:
return cached
family = ReactionFamily(label=label, consider_arc_families=consider_arc_families)
_REACTION_FAMILY_CACHE[key] = family
return family
Copy link

Copilot AI Jan 26, 2026

Choose a reason for hiding this comment

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

_REACTION_FAMILY_CACHE stores ReactionFamily instances globally with no eviction. These objects hold groups_as_lines and derived data, so this can retain significant memory over a long-running process and makes behavior dependent on global cache state. Consider adding a bounded/LRU cache, an explicit cache clear mechanism, or limiting caching to call sites that can guarantee reuse (instead of caching unconditionally at module scope).

Copilot uses AI. Check for mistakes.
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 9 out of 9 changed files in this pull request and generated 3 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

arc/processor.py Outdated
Comment on lines 276 to 287
thermo_info_path = os.path.join(output_directory, 'thermo.info')
if not os.path.isfile(thermo_info_path):
labels = [spc.label for spc in species_to_compare]
max_label_len = max((len(label) for label in labels), default=0)
thermo_sources = '\nSources of thermodynamic properties determined by RMG for the parity plots:\n'
for spc in species_to_compare:
comment = spc.rmg_thermo.comment
thermo_sources += ' {0}: {1}{2}\n'.format(spc.label,
' ' * (max_label_len - len(spc.label)),
comment)
with open(thermo_info_path, 'w') as f:
f.write(thermo_sources)
Copy link

Copilot AI Jan 27, 2026

Choose a reason for hiding this comment

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

This block is effectively dead/redundant: plotter.draw_thermo_parity_plots(..., path=output_directory) already writes thermo.info unconditionally (see arc/plotter.py:458), so if not os.path.isfile(thermo_info_path) will almost never be true. Consider removing this duplicate thermo.info generation logic, or move/centralize the thermo.info formatting in the plotter if you need different behavior.

Suggested change
thermo_info_path = os.path.join(output_directory, 'thermo.info')
if not os.path.isfile(thermo_info_path):
labels = [spc.label for spc in species_to_compare]
max_label_len = max((len(label) for label in labels), default=0)
thermo_sources = '\nSources of thermodynamic properties determined by RMG for the parity plots:\n'
for spc in species_to_compare:
comment = spc.rmg_thermo.comment
thermo_sources += ' {0}: {1}{2}\n'.format(spc.label,
' ' * (max_label_len - len(spc.label)),
comment)
with open(thermo_info_path, 'w') as f:
f.write(thermo_sources)

Copilot uses AI. Check for mistakes.
Comment on lines 838 to 848
def _get_entries_map(groups_as_lines: List[str]) -> Dict[str, str]:
signature = (len(groups_as_lines), groups_as_lines[0] if groups_as_lines else '', groups_as_lines[-1] if groups_as_lines else '')
cache_key = id(groups_as_lines)
cached = _ENTRIES_CACHE.get(cache_key)
if cached is not None and cached[0] == signature:
return cached[1]
groups_str = ''.join(groups_as_lines)
entries_map: Dict[str, str] = {}
Copy link

Copilot AI Jan 27, 2026

Choose a reason for hiding this comment

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

_get_entries_map() caches by id(groups_as_lines) and only validates via a very weak (len, first, last) signature. This can return incorrect results if a different list instance later reuses the same id (possible after GC) and happens to match the signature, and _ENTRIES_CACHE will also grow without bound over time. Prefer caching by stable content (e.g., hash/tuple of lines or the groups file path + mtime) or use a bounded/LRU cache; alternatively remove this cache since get_reaction_family() now caches ReactionFamily instances.

Copilot uses AI. Check for mistakes.
Adds a configuration option to display test durations directly in pytest reports.

This enhances the feedback provided during testing by showing how long each test takes, helping to identify slow tests.
Improves performance by caching reaction family entries and
utilizing precompiled regular expressions.

Adds a cache for reaction family entries to avoid redundant file
parsing and regex operations.
Introduces a fingerprinting mechanism to ensure cache validity.
Introduces a worker suffix to project names in restart tests to avoid conflicts when running tests in parallel using xdist. This ensures that each worker has its own isolated project directory, preventing interference and ensuring accurate test results.
Improves the readability of inline duration reports in pytest by
prefixing the duration with the test outcome (PASSED, FAILED,
SKIPPED). This makes it easier to quickly identify the status of
each test when durations are displayed inline.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants