Skip to content
Merged
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
32 changes: 32 additions & 0 deletions launchable/test_runners/codeceptjs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import json
from typing import List

import click

from ..testpath import TestPath
from . import launchable


@launchable.subset
def subset(client):
def handler(output: List[TestPath], rests: List[TestPath]):
# The output would be something like this:
# {"tests": ["test/example_test.js", "test/login_test.js"]}
if client.rest:
with open(client.rest, "w+", encoding="utf-8") as f:
f.write(json.dumps({"tests": [client.formatter(t) for t in rests]}))
if output:
click.echo(json.dumps({"tests": [client.formatter(t) for t in output]}))

# read lines as test file names
for t in client.stdin():
if t.rstrip("\n"):
client.test_path(t.rstrip("\n"))
client.output_handler = handler

client.run()


record_tests = launchable.CommonRecordTestImpls(__name__).file_profile_report_files()

split_subset = launchable.CommonSplitSubsetImpls(__name__).split_subset()
37 changes: 1 addition & 36 deletions launchable/test_runners/file.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
#
# The most bare-bone versions of the test runner support
#
import click
from junitparser import TestCase, TestSuite # type: ignore

from ..testpath import TestPath
from . import launchable


Expand All @@ -17,39 +14,7 @@ def subset(client):
client.run()


@click.argument('reports', required=True, nargs=-1)
@launchable.record.tests
def record_tests(client, reports):
def path_builder(case: TestCase, suite: TestSuite,
report_file: str) -> TestPath:
"""path builder that puts the file name first, which is consistent with the subset command"""
def find_filename():
"""look for what looks like file names from test reports"""
for e in [case, suite]:
for a in ["file", "filepath"]:
filepath = e._elem.attrib.get(a)
if filepath:
return filepath
return None # failing to find a test name

filepath = find_filename()
if not filepath:
raise click.ClickException(
"No file name found in %s" % report_file)

# default test path in `subset` expects to have this file name
test_path = [client.make_file_path_component(filepath)]
if suite.name:
test_path.append({"type": "testsuite", "name": suite.name})
if case.name:
test_path.append({"type": "testcase", "name": case.name})
return test_path
client.path_builder = path_builder

for r in reports:
client.report(r)
client.run()

record_tests = launchable.CommonRecordTestImpls(__name__).file_profile_report_files()

split_subset = launchable.CommonSplitSubsetImpls(__name__).split_subset()

Expand Down
44 changes: 44 additions & 0 deletions launchable/test_runners/launchable.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,15 @@
import types

import click
from junitparser import TestCase, TestSuite # type: ignore

from launchable.commands.detect_flakes import detect_flakes as detect_flakes_cmd
from launchable.commands.record.tests import tests as record_tests_cmd
from launchable.commands.split_subset import split_subset as split_subset_cmd
from launchable.commands.subset import subset as subset_cmd

from ..testpath import TestPath


def cmdname(m):
"""figure out the sub-command name from a test runner function"""
Expand Down Expand Up @@ -113,6 +116,47 @@ def record_tests(client, source_roots):

return wrap(record_tests, record_tests_cmd, self.cmdname)

def file_profile_report_files(self):
"""
Suitable for test runners that create a directory full of JUnit report files.

'record tests' expect JUnit report/XML file names.
"""

@click.argument('source_roots', required=True, nargs=-1)
def record_tests(client, source_roots):
def path_builder(
case: TestCase, suite: TestSuite, report_file: str
) -> TestPath:
def find_filename():
"""look for what looks like file names from test reports"""
for e in [case, suite]:
for a in ["file", "filepath"]:
filepath = e._elem.attrib.get(a)
if filepath:
return filepath
return None # failing to find a test name

filepath = find_filename()
if not filepath:
raise click.ClickException("No file name found in %s" % report_file)

# default test path in `subset` expects to have this file name
test_path = [client.make_file_path_component(filepath)]
if suite.name:
test_path.append({"type": "testsuite", "name": suite.name})
if case.name:
test_path.append({"type": "testcase", "name": case.name})
return test_path

client.path_builder = path_builder

for r in source_roots:
client.report(r)
client.run()

return wrap(record_tests, record_tests_cmd, self.cmdname)

@classmethod
def load_report_files(cls, client, source_roots, file_mask="*.xml"):
# client type: RecordTests in def launchable.commands.record.tests.tests
Expand Down
18 changes: 18 additions & 0 deletions tests/data/codeceptjs/codeceptjs-result.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<testsuites name="Mocha Tests" time="0.861" tests="2" failures="1">
<testsuite name="Root Suite" timestamp="2025-10-27T01:20:06" tests="0" time="0.000" failures="0">
</testsuite>
<testsuite name="Basic Tests" timestamp="2025-10-27T01:20:06" tests="2" file="/Users/ono-max/src/github.com/cloudbees-oss/smart-tests-integration-examples/codeceptjs/tests/website_test.js" time="0.861" failures="1">
<testcase name="Basic Tests: Visit homepage" time="0.194" classname="Visit homepage">
<failure message="expected web application to include &quot;Test Pagee&quot;" type="Error"><![CDATA[expected web application to include "Test Pagee"

+ expected - actual

-Test Page Welcome
+Test Pagee
]]></failure>
</testcase>
<testcase name="Basic Tests: Visit form page" time="0.127" classname="Visit form page">
</testcase>
</testsuite>
</testsuites>
53 changes: 53 additions & 0 deletions tests/data/codeceptjs/record_test_result.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
{
"events": [
{
"type": "case",
"testPath": [
{
"type": "file",
"name": "/Users/ono-max/src/github.com/cloudbees-oss/smart-tests-integration-examples/codeceptjs/tests/website_test.js"
},
{
"type": "testsuite",
"name": "Basic Tests"
},
{
"type": "testcase",
"name": "Basic Tests: Visit homepage"
}
],
"duration": 0.194,
"status": 0,
"stdout": "",
"stderr": "expected web application to include \"Test Pagee\"\n\n + expected - actual\n\n -Test Page Welcome\n +Test Pagee\n ",
"data": null
},
{
"type": "case",
"testPath": [
{
"type": "file",
"name": "/Users/ono-max/src/github.com/cloudbees-oss/smart-tests-integration-examples/codeceptjs/tests/website_test.js"
},
{
"type": "testsuite",
"name": "Basic Tests"
},
{
"type": "testcase",
"name": "Basic Tests: Visit form page"
}
],
"duration": 0.127,
"status": 1,
"stdout": "",
"stderr": "",
"data": null
}
],
"testRunner": "codeceptjs",
"group": "",
"noBuild": false,
"flavors": [],
"testSuite": ""
}
Loading
Loading