Discovery plugins¶
MethodAtlas discovers test methods through a plugin system. Each plugin handles
one language or test framework. Plugins are loaded automatically at startup via
ServiceLoader — placing a plugin JAR on the classpath is enough to activate it.
This page maps the general configuration terms to what they mean in each supported language so you know exactly what to put in your YAML or on the command line.
Configuration model¶
Every plugin receives three pieces of configuration:
| Config knob | CLI flag | YAML key | What it does |
|---|---|---|---|
| File suffixes | -file-suffix |
fileSuffixes |
Selects which files the plugin should read. Universal — every language has files with extensions. May be prefixed with a plugin ID to target one plugin (see below). |
| Test markers | -test-marker |
testMarkers |
Names the identifiers that mark a method as a test. The meaning of "marker" is language-specific (see below). Empty = use plugin defaults. |
| Properties | -property key=value |
properties |
Open-ended key/value pairs for plugin-specific settings that do not fit either field above. Plugins ignore keys they do not recognise. |
Targeting a suffix at one plugin¶
By default a -file-suffix (or fileSuffixes: entry) is delivered to every
loaded plugin. To restrict a suffix to a single plugin, prefix the value with the
plugin's ID and a colon (:):
# "Test.java" → java plugin only; "Test.cs" → dotnet plugin only
./methodatlas \
-file-suffix java:Test.java \
-file-suffix dotnet:Test.cs \
src/
The colon character is the separator because it cannot appear in a file name on any mainstream operating system (Windows, macOS, Linux). A value without a colon is global and goes to every plugin, which is the correct choice for most single- language projects.
| Example value | Delivered to |
|---|---|
Test.java |
all plugins |
java:Test.java |
java plugin only |
dotnet:BadNaming.cs |
dotnet plugin only |
Built-in plugin IDs: java (methodatlas-discovery-jvm), dotnet
(methodatlas-discovery-dotnet), typescript
(methodatlas-discovery-typescript), go
(methodatlas-discovery-go), python
(methodatlas-discovery-python), and powershell
(methodatlas-discovery-powershell). Third-party plugins declare their own ID
via TestDiscovery.pluginId().
If no matching suffix reaches a plugin (e.g. all entries are targeted at other
plugins), that plugin falls back to its language-specific built-in default
(Test.java for Java; .cs for .NET).
Java and Kotlin (JVM)¶
Plugin class: org.egothor.methodatlas.discovery.jvm.JavaTestDiscovery
In Java and Kotlin, a test marker is an annotation simple name.
Default behaviour — automatic framework detection¶
When testMarkers is empty (the default), the plugin inspects each file's
import declarations and picks the right annotation set automatically:
| Imports present | Annotation set used |
|---|---|
org.junit.jupiter.* |
Test, ParameterizedTest, RepeatedTest, TestFactory, TestTemplate |
org.junit.* or junit.framework.* |
All JUnit 5 annotations plus Theory |
org.testng.* |
Test |
| None of the above | JUnit 5 set (fallback) |
Detection is per-file and accumulative: a file that imports both JUnit 4 and JUnit 5 (common during migrations) receives the union of both sets.
Overriding the detected annotation set¶
Supply annotation simple names with -test-marker or testMarkers. The first
value replaces the entire default set; subsequent values append to it.
Common marker values by framework¶
| Framework | Marker values to use |
|---|---|
| JUnit 5 (Jupiter) | Test, ParameterizedTest, RepeatedTest, TestFactory, TestTemplate |
| JUnit 4 | Test, Theory |
| TestNG | Test |
| Custom | The simple name of your annotation (without @) |
| Mixed JUnit 4 + 5 | Leave testMarkers empty — auto-detection handles this |
The properties map (JVM)¶
The JVM plugin does not use the properties map. Any keys present are silently
ignored.
C# / .NET¶
Plugin class: org.egothor.methodatlas.discovery.dotnet.DotNetTestDiscovery
In C#, a test marker is an attribute simple name (without the surrounding
[ ] brackets). Framework detection is automatic from using directives —
leave testMarkers empty unless you need to override it.
| Framework | Detected by | Default markers |
|---|---|---|
| xUnit | using Xunit; |
Fact, Theory |
| NUnit | using NUnit.Framework; |
Test, TestCase, TestCaseSource |
| MSTest | using Microsoft.VisualStudio.TestTools.UnitTesting; |
TestMethod, DataTestMethod |
Tag reading¶
When MethodAtlas scans a .cs file it reads existing category / trait
attributes and records their values in the tags column:
| Framework | Attribute read | Argument used |
|---|---|---|
| NUnit | [Category("value")] |
first (and only) argument |
| xUnit | [Trait("Tag", "value")] or [Trait("Category", "value")] |
second argument (first must be "Tag" or "Category", case-insensitive) |
| MSTest | [TestCategory("value")] |
first (and only) argument |
Tag and display-name write-back¶
The .NET plugin also ships a SourcePatcher
(DotNetSourcePatcher) that writes annotation changes back into .cs files:
| Framework | Tag written as | Display name |
|---|---|---|
| NUnit | [Category("value")] |
not supported |
| xUnit | [Trait("Tag", "value")] |
[Fact(DisplayName = "text")] |
| MSTest | [TestCategory("value")] |
not supported |
Configuration example¶
The properties map (.NET)¶
The .NET plugin does not currently use the properties map. Any keys present
are silently ignored.
Parser scope¶
The C# parser is built on a structural ANTLR4 grammar (CSharpTest.g4) that
focuses on namespaces, type declarations, method declarations, and attribute
sections. Method bodies are treated as opaque balanced-brace content. This covers
the overwhelming majority of real-world test files, but the grammar is not a
full implementation of the C# language specification and may not handle every
exotic syntax construct.
When the grammar cannot parse a construct it logs a WARNING that includes the
file path, line number, character position, and a description of the problem.
ANTLR4 error recovery then continues, so as many test methods as possible are
still discovered from the remainder of the file.
If you encounter a parse warning on a valid .cs file, please report it and
include the relevant code fragment. Grammar improvements are localised and
typically quick to deliver.
TypeScript and JavaScript¶
Plugin class: org.egothor.methodatlas.discovery.typescript.TypeScriptTestDiscovery
Module: methodatlas-discovery-typescript
Requires: Node.js 18 or later on the PATH
TypeScript testing frameworks (Jest, Vitest, Mocha) do not use annotations
or decorators to mark tests. Tests are identified by function call names
(test(…), it(…)). The testMarkers field is not applicable here — leave it
empty and use properties instead.
Prerequisites¶
Node.js 18 or later must be installed and accessible as node on the system
PATH. If Node.js is absent or below version 18, the plugin disables itself
gracefully and logs a warning; all other plugins continue to function normally.
Default file suffixes¶
The plugin matches files ending in .test.ts, .spec.ts, .test.tsx,
.spec.tsx, .test.js, or .spec.js by default. These defaults are
plugin-scoped — they are not delivered to the Java or .NET plugins.
Test-method detection¶
describe, context, and suite calls are recognised as scope containers.
The container name is prepended to the test name in the output:
| Source | Method recorded as |
|---|---|
it('should auth') at top level |
should auth |
describe('AuthService', () => { it('should auth', ...) }) |
AuthService > should auth |
| Nested describes | Outer > Inner > test name |
The test.each, it.each, test.skip, test.only variants are also
recognised — the base function name is extracted from the member expression.
Configuration properties¶
| Property key | Meaning | Default values |
|---|---|---|
functionNames |
Function call names that identify a test | test, it |
typescript.poolSize |
Number of Node.js worker processes | min(4, CPU count) |
typescript.workerTimeoutSec |
Per-file worker timeout in seconds | 30 |
typescript.maxConsecutiveRestarts |
Circuit-breaker restart limit | 5 |
typescript.restartWindowSec |
Circuit-breaker sliding window in seconds | 60 |
# Auto-detected defaults — works for Jest, Vitest, Mocha out of the box
./methodatlas \
-file-suffix typescript:.test.ts \
-file-suffix typescript:.spec.ts \
src/
# Custom function names (Jasmine uses 'it' only; custom frameworks vary)
./methodatlas \
-file-suffix typescript:.spec.ts \
-property functionNames=it \
-property functionNames=specify \
src/
Security model¶
The TypeScript plugin runs a pre-built, integrity-verified Node.js process for each scan. The security guarantees are:
Bundle integrity — the SHA-256 of the scanner bundle is computed at Gradle
build time and embedded in the JAR manifest as TS-Scanner-Bundle-SHA256. At
startup the plugin re-computes the hash and refuses to run if it does not match.
This detects JAR tampering and corruption before any code executes.
No runtime npm — the bundle (ts-scanner.bundle.js) is a single
self-contained file produced by esbuild at build time. No package manager runs
at runtime; the only Node.js module loaded is the bundle itself.
File-system sandboxing — when Node.js 20 or later is detected, workers are
started with --experimental-permission --allow-fs-read=<scan-root> so the
worker process can only read files under the directories being scanned.
Audit logging — every worker start and stop is logged at INFO level with
the bundle version, SHA-256 prefix, Node.js version, and OS process ID. Every
worker kill is logged with the reason. This gives audit teams a full provenance
trail for any MethodAtlas run.
Circuit breaker — if a worker restarts more than
typescript.maxConsecutiveRestarts times within typescript.restartWindowSec
seconds, the plugin is disabled for the remainder of the run and a WARNING is
emitted. This prevents runaway restart loops from consuming system resources.
The properties map (TypeScript)¶
| Key | Type | Default | Description |
|---|---|---|---|
functionNames |
List<String> |
test, it |
Function call names that identify a test method. Repeated -property functionNames=… values accumulate into a list. |
typescript.poolSize |
int |
min(4, CPUs) |
Worker-pool size. Increase for projects with very many test files. |
typescript.workerTimeoutSec |
int |
30 |
Per-file response timeout. Increase for large files or slow machines. |
typescript.maxConsecutiveRestarts |
int |
5 |
Circuit-breaker restart limit. |
typescript.restartWindowSec |
int |
60 |
Circuit-breaker sliding window width. |
Go¶
Plugin class: org.egothor.methodatlas.discovery.go.GoTestDiscovery
Module: methodatlas-discovery-go
Go test functions are discovered by parsing each _test.go file with a
structural ANTLR4 grammar (GoTest.g4) and matching function declarations
against the standard go test convention.
Test function detection¶
A function is treated as a test if its declaration matches:
where Xxx starts with an upper-case letter or underscore. Benchmark functions
(BenchmarkXxx), example functions (ExampleXxx), and fuzz targets
(FuzzXxx) are intentionally excluded and will not appear in the output.
Default file suffix¶
_test.go (configurable via -file-suffix go:_test.go).
Tags and display names¶
Go has no annotation-based tag or display-name system. The tags column is
always empty and display_name is always blank. The testMarkers field has
no effect on this plugin and should be left empty.
Configuration example¶
The properties map (Go)¶
The Go plugin does not use the properties map. Any keys present are silently
ignored.
Python¶
Plugin class: org.egothor.methodatlas.discovery.python.PythonTestDiscovery
Module: methodatlas-discovery-python
Requires: Python 3.8 or later on the PATH
The Python plugin discovers test functions and methods following the
pytest naming conventions. Parsing is performed
by a pool of long-lived Python worker processes that run the bundled
py-scanner.py script using the standard-library ast module. The AST-based
approach correctly handles all valid Python syntax — including decorator stacks,
type annotations, and async functions — and reports exact begin/end line numbers.
Prerequisites¶
Python 3.8 or later must be installed and accessible as python3 (or python)
on the system PATH. Python 3.8 is the minimum because ast.Node.end_lineno
— used to compute per-function line ranges — was added in that release. If
Python is absent or below version 3.8, the plugin disables itself gracefully and
logs a warning; all other plugins continue to function normally.
File selection¶
Two file-naming conventions are supported by default:
| Convention | Example | Active when |
|---|---|---|
test_*.py prefix |
test_auth.py |
Always — cannot be disabled via CLI |
*_test.py suffix |
security_test.py |
Default when no -file-suffix python:… is set |
If -file-suffix python:<suffix> is supplied, the suffix check uses that value
instead of the default _test.py. The test_ prefix check remains active
regardless.
Test function and method detection¶
The plugin recognises:
- Module-level functions — any
def test_*()orasync def test_*()function at module scope. - Class methods —
def test_*()orasync def test_*()methods inside a class whose name starts withTest, ends withTest, or ends withTests(e.g.TestAuth,AuthTest,AuthTests).
The fully-qualified class name (FQCN) is the dot-separated module path for
module-level functions, or the module path suffixed with the class name for
methods (auth.test_auth.TestAuth).
Tags from @pytest.mark¶
Decorator lines immediately before a def test_* are inspected. Any
@pytest.mark.<name> decorator contributes name to the tags column. All
other decorators are ignored.
emits tags = security;slow.
testMarkers — not applicable¶
pytest identifies tests by function name, not by annotations. The testMarkers
field has no effect on this plugin and should be left empty.
Configuration example¶
Configuration properties (Python)¶
| Property key | Meaning | Default |
|---|---|---|
python.poolSize |
Number of Python worker processes | min(2, CPU count) |
python.workerTimeoutSec |
Per-file worker timeout in seconds | 30 |
python.maxConsecutiveRestarts |
Circuit-breaker restart limit | 5 |
python.restartWindowSec |
Circuit-breaker sliding window in seconds | 60 |
The properties map (Python)¶
| Key | Type | Default | Description |
|---|---|---|---|
python.poolSize |
int |
min(2, CPUs) |
Worker-pool size. |
python.workerTimeoutSec |
int |
30 |
Per-file response timeout in seconds. |
python.maxConsecutiveRestarts |
int |
5 |
Circuit-breaker restart limit. |
python.restartWindowSec |
int |
60 |
Circuit-breaker sliding window in seconds. |
PowerShell / Pester¶
Plugin class: org.egothor.methodatlas.discovery.powershell.PowerShellTestDiscovery
Module: methodatlas-discovery-powershell
The PowerShell plugin discovers Pester test cases from It "…" { … } blocks
in PowerShell test scripts. No PowerShell runtime is required — parsing is
performed entirely in Java using a structural ANTLR4 grammar
(PowerShellTest.g4) that covers Describe, Context, and It blocks while
treating all other PowerShell content as opaque tokens.
File selection¶
Default suffixes (both are active unless overridden):
| Suffix | Example |
|---|---|
.Tests.ps1 |
Auth.Tests.ps1 |
.Test.ps1 |
Auth.Test.ps1 |
Override with -file-suffix powershell:<suffix> to use a different convention
(e.g. .ps1 alone for projects that do not follow the Pester suffix convention).
Test case detection¶
Every It "description" { … } block is emitted as one discovered method,
regardless of nesting level. It is matched case-insensitively to support
style variants.
Describe "…" and Context "…" blocks are not emitted as separate records —
they are used only to derive the FQCN (directory + file stem joined with .).
Tags from -Tag¶
The -Tag parameter on a Pester Describe, Context, or It block is read
and its values are collected into the tags column:
Describe "Auth Module" -Tag "security", "auth" {
It "rejects expired tokens" -Tag "regression" {
...
}
}
Tags are collected at the It line only. Tags on enclosing Describe/Context
blocks are not propagated to child It entries — Pester itself handles tag
inheritance at runtime.
testMarkers — not applicable¶
Pester uses the It keyword, not annotations or attributes. The testMarkers
field has no effect on this plugin and should be left empty.
Configuration example¶
The properties map (PowerShell)¶
The PowerShell plugin does not use the properties map. Any keys present are
silently ignored.
SAP ABAP / ABAP Unit / ecATT¶
Plugin class: org.egothor.methodatlas.discovery.abap.ABAPTestDiscovery
Module: methodatlas-discovery-abap
The ABAP plugin discovers tests from two SAP test conventions in a single
pass — no SAP NetWeaver runtime is required. Parsing is performed entirely
in Java using two ANTLR4 grammars (ABAPTest.g4 and ECATTScript.g4)
loaded by the same plugin.
Conventions detected¶
| Convention | File extension | What is emitted |
|---|---|---|
| ABAP Unit | .abap |
Each method declared FOR TESTING inside a class flagged FOR TESTING |
| ecATT | .ecl |
Each FUNCTION … DONE block in an exported ecATT script (transaction SECATT) |
File selection¶
Default suffixes (both are active unless overridden):
| Suffix | Example |
|---|---|
.abap |
zcl_auth_test.abap |
.ecl |
zecatt_login.ecl |
Override with -file-suffix abap:<suffix> to restrict or extend the
selection (for example, projects that store ABAP Unit classes under a
non-default extension produced by an export tool).
ABAP Unit — detection logic¶
The plugin walks the lexed token stream rather than a full parse tree. This is deliberate: ABAP has an extremely large statement vocabulary and a strict structural grammar is brittle inside method bodies. Token scanning reacts only to a handful of well-defined markers and ignores everything else.
The scanner emits one discovered method when all three of these hold:
- A class is opened with
CLASS … DEFINITION … FOR TESTING. - A
METHODS:declaration inside that class names a method with theFOR TESTINGattribute. - The corresponding
CLASS … IMPLEMENTATION.block contains aMETHOD <name> … ENDMETHOD.body for that name.
The FQCN is the ABAP class name as written in the source (for example
ZCL_AUTH_TEST).
ecATT — detection logic¶
Each FUNCTION <name> … DONE block is emitted as one test case. The
function name is used as the method name; begin and end lines span from
the FUNCTION keyword to the closing DONE. The FQCN is the script file
stem.
testMarkers — not applicable¶
ABAP Unit uses the FOR TESTING keyword pair (structurally recognised);
ecATT uses the FUNCTION keyword. The testMarkers field has no effect
on either detector and should be left empty.
The properties map (ABAP)¶
The ABAP plugin does not currently use the properties map. Keys present
are silently ignored, reserved for future SAP-tool-specific options.
Configuration example¶
COBOL — MFUnit / COBOL-Check¶
Plugin class: org.egothor.methodatlas.discovery.cobol.COBOLTestDiscovery
Module: methodatlas-discovery-cobol
The COBOL plugin discovers tests from the two prevailing COBOL unit-test
conventions in a single pass — no COBOL compiler or mainframe runtime is
required. Parsing uses an ANTLR4 grammar (COBOLTest.g4) and a
token-stream scanner that is robust to free-format and fixed-format COBOL
sources alike.
Conventions detected¶
| Convention | File extensions | What is emitted |
|---|---|---|
| Micro Focus MFUnit | .cbl, .cob, .cobol |
Each PROCEDURE DIVISION paragraph whose name begins with MFU-TC- |
| COBOL-Check | .cut, also mixed in .cbl |
Each TestCase '<name>' directive (TestSuite directives are recognised but not emitted) |
File selection¶
Default suffixes (all four are active unless overridden):
| Suffix | Example |
|---|---|
.cbl |
auth-tests.cbl |
.cob |
auth-tests.cob |
.cobol |
auth-tests.cobol |
.cut |
auth.cut (COBOL-Check) |
MFUnit — detection logic¶
When the lexer recognises an MFU-TC-<rest> paragraph header, the
plugin records it as a discovered test paragraph. End-line is the line of
the next MFU paragraph header (or end-of-file), minus one.
COBOL-Check — detection logic¶
When the lexer recognises a TestCase keyword followed by a quoted
string, the value with quotes stripped is recorded as the test name.
End-line spans to the next TestCase / TestSuite header or
end-of-file.
FQCN computation¶
The FQCN is derived from the file path relative to the scan root
(dot-separated, extension stripped). When the PROGRAM-ID paragraph can
be extracted from the source, it is appended as the outermost qualifier;
otherwise the file stem alone is used.
testMarkers — not applicable¶
Neither convention uses identifier markers in the JUnit/xUnit sense.
MFU-TC-* and TestCase are recognised structurally; testMarkers has
no effect and should be left empty.
The properties map (COBOL)¶
The COBOL plugin does not currently use the properties map. Keys
present are silently ignored, reserved for future dialect-specific options
(for example, fixed-format column thresholds).
Configuration example¶
Quick reference¶
| Language / framework | fileSuffixes |
testMarkers |
properties |
|---|---|---|---|
| Java — JUnit 5 | Test.java |
(leave empty — auto-detected) | — |
| Java — TestNG | Test.java |
(leave empty — auto-detected) | — |
| Java — custom annotation | Test.java |
e.g. ScenarioTest |
— |
| Kotlin — JUnit 5 | Test.kt |
(leave empty — auto-detected) | — |
| C# — xUnit | dotnet:Test.cs |
Fact, Theory |
— |
| C# — NUnit | dotnet:Test.cs |
Test, TestCase |
— |
| TypeScript — Jest | typescript:.test.ts, typescript:.spec.ts |
(leave empty) | functionNames=test, functionNames=it |
| TypeScript — Vitest | typescript:.test.ts, typescript:.spec.ts |
(leave empty) | functionNames=test, functionNames=it |
| TypeScript — Mocha | typescript:.test.ts, typescript:.spec.ts |
(leave empty) | functionNames=it |
| JavaScript — Jest | typescript:.test.js, typescript:.spec.js |
(leave empty) | functionNames=test, functionNames=it |
| Go — testing package | go:_test.go |
(not applicable) | — |
| Python — pytest functions | python:_test.py |
(leave empty — name-based) | — |
| Python — pytest classes | python:_test.py |
(leave empty — name-based) | — |
| PowerShell — Pester | powershell:.Tests.ps1, powershell:.Test.ps1 |
(not applicable) | — |
| SAP ABAP — ABAP Unit | abap:.abap, abap:.ecl |
(not applicable — FOR TESTING is recognised structurally) |
— |
| COBOL — MFUnit / COBOL-Check | cobol:.cbl, cobol:.cob, cobol:.cobol, cobol:.cut |
(not applicable — MFU-TC-* paragraphs and TestCase directives are recognised structurally) |
— |
Source write-back (SourcePatcher) support¶
A second SPI, org.egothor.methodatlas.api.SourcePatcher, is used by the
-apply-tags and -apply-tags-from-csv modes to write annotation decisions
back into source files. Not every discovery plugin ships one — and that is by
design: the SPI is only implemented where MethodAtlas can do a safe,
formatting-preserving edit.
| Plugin | Discovery | SourcePatcher |
What -apply-tags does |
|---|---|---|---|
| jvm | ✓ | JavaSourcePatcher |
Inserts @DisplayName and @Tag via JavaParser, preserving comments and whitespace |
| dotnet | ✓ | DotNetSourcePatcher |
Inserts [Category] / [Trait] / [TestCategory] and xUnit DisplayName= |
| typescript | ✓ | (none) | File is recognised but skipped with a per-file notice |
| go | ✓ | (none) | File is recognised but skipped with a per-file notice |
| python | ✓ | (none) | File is recognised but skipped with a per-file notice |
| powershell | ✓ | (none) | File is recognised but skipped with a per-file notice |
| abap | ✓ | (none) | File is recognised but skipped with a per-file notice |
| cobol | ✓ | (none) | File is recognised but skipped with a per-file notice |
The skip notice has the form:
Apply-tags: skipped <path> — source write-back is not supported for this language (currently Java and C# only)
and the completion summary appends an aggregate skip count when at least one
file was skipped. Skipped files do not cause a non-zero exit code; treat the
constraint as a known scope limit, not an error. The GUI gates the Stage and
"Accept All AI Tags" buttons identically: if the currently selected method
lives in a file whose language has no SourcePatcher, the buttons are disabled
and a tooltip explains that source write-back is not available for that
language.
If you need to add SourcePatcher support for an additional language,
implement the SPI (see
SourcePatcher.java)
and register it in META-INF/services/org.egothor.methodatlas.api.SourcePatcher
inside your plugin JAR.
See also¶
- CLI Options —
-test-marker— full flag documentation including auto-detection details - CLI Options —
-property— property flag reference - Static Inventory — scanning workflow
- CLI Examples — practical command-line examples