Skip to content

Commit

Permalink
Merge pull request #1491 from Jaseci-Labs/thakee-jac-test-improve
Browse files Browse the repository at this point in the history
jac test runs for specific file and func
  • Loading branch information
marsninja authored Jan 6, 2025
2 parents 9533beb + b99649f commit d049a8c
Show file tree
Hide file tree
Showing 8 changed files with 125 additions and 7 deletions.
3 changes: 3 additions & 0 deletions jac/jaclang/cli/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -307,6 +307,7 @@ def enter(
@cmd_registry.register
def test(
filepath: str,
test_name: str = "",
filter: str = "",
xit: bool = False,
maxfail: int = None, # type:ignore
Expand All @@ -316,6 +317,7 @@ def test(
"""Run the test suite in the specified .jac file.
:param filepath: Path/to/file.jac
:param test_name: Run a specific test.
:param filter: Filter the files using Unix shell style conventions.
:param xit(exit): Stop(exit) running tests as soon as finds an error.
:param maxfail: Stop running tests after n failures.
Expand All @@ -328,6 +330,7 @@ def test(

failcount = Jac.run_test(
filepath=filepath,
func_name=("test_" + test_name) if test_name else None,
filter=filter,
xit=xit,
maxfail=maxfail,
Expand Down
14 changes: 11 additions & 3 deletions jac/jaclang/plugin/default.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import ast as ast3
import fnmatch
import html
import inspect
import os
import types
from collections import OrderedDict
Expand Down Expand Up @@ -818,19 +819,22 @@ def jac_import(
@hookimpl
def create_test(test_fun: Callable) -> Callable:
"""Create a new test."""
file_path = inspect.getfile(test_fun)
func_name = test_fun.__name__

def test_deco() -> None:
test_fun(JacTestCheck())

test_deco.__name__ = test_fun.__name__
JacTestCheck.add_test(test_deco)
JacTestCheck.add_test(file_path, func_name, test_deco)

return test_deco

@staticmethod
@hookimpl
def run_test(
filepath: str,
func_name: Optional[str],
filter: Optional[str],
xit: bool,
maxfail: Optional[int],
Expand All @@ -849,7 +853,9 @@ def run_test(
mod_name = mod_name[:-5]
JacTestCheck.reset()
Jac.jac_import(target=mod_name, base_path=base, cachable=False)
JacTestCheck.run_test(xit, maxfail, verbose)
JacTestCheck.run_test(
xit, maxfail, verbose, os.path.abspath(filepath), func_name
)
ret_count = JacTestCheck.failcount
else:
print("Not a .jac file.")
Expand All @@ -875,7 +881,9 @@ def run_test(
print(f"\n\n\t\t* Inside {root_dir}" + "/" + f"{file} *")
JacTestCheck.reset()
Jac.jac_import(target=file[:-4], base_path=root_dir)
JacTestCheck.run_test(xit, maxfail, verbose)
JacTestCheck.run_test(
xit, maxfail, verbose, os.path.abspath(file), func_name
)

if JacTestCheck.breaker and (xit or maxfail):
break
Expand Down
2 changes: 2 additions & 0 deletions jac/jaclang/plugin/feature.py
Original file line number Diff line number Diff line change
Expand Up @@ -359,6 +359,7 @@ def create_test(test_fun: Callable) -> Callable:
@staticmethod
def run_test(
filepath: str,
func_name: Optional[str] = None,
filter: Optional[str] = None,
xit: bool = False,
maxfail: Optional[int] = None,
Expand All @@ -368,6 +369,7 @@ def run_test(
"""Run the test suite in the specified .jac file."""
return plugin_manager.hook.run_test(
filepath=filepath,
func_name=func_name,
filter=filter,
xit=xit,
maxfail=maxfail,
Expand Down
1 change: 1 addition & 0 deletions jac/jaclang/plugin/spec.py
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,7 @@ def create_test(test_fun: Callable) -> Callable:
@hookspec(firstresult=True)
def run_test(
filepath: str,
func_name: Optional[str],
filter: Optional[str],
xit: bool,
maxfail: Optional[int],
Expand Down
63 changes: 59 additions & 4 deletions jac/jaclang/runtimelib/test.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from __future__ import annotations

import unittest
from dataclasses import dataclass
from typing import Callable, Optional


Expand Down Expand Up @@ -56,6 +57,16 @@ class JacTestCheck:

test_case = unittest.TestCase()
test_suite = unittest.TestSuite()

@dataclass
class TestSuite:
"""Test Suite."""

test_case: unittest.FunctionTestCase
func_name: str

test_suite_path: dict[str, list[TestSuite]] = {}

breaker = False
failcount = 0

Expand All @@ -64,13 +75,45 @@ def reset() -> None:
"""Clear the test suite."""
JacTestCheck.test_case = unittest.TestCase()
JacTestCheck.test_suite = unittest.TestSuite()
JacTestCheck.test_suite_path = {}

@staticmethod
def run_test(xit: bool, maxfail: int | None, verbose: bool) -> None:
def run_test(
xit: bool,
maxfail: int | None,
verbose: bool,
filepath: str | None,
func_name: str | None,
) -> None:
"""Run the test suite."""
verb = 2 if verbose else 1
test_suite = JacTestCheck.test_suite

if filepath and filepath.endswith(".test.jac"):
filepath = filepath[:-9]
elif filepath and filepath.endswith(".jac"):
filepath = filepath[:-4]

if filepath:
test_cases = JacTestCheck.test_suite_path.get(filepath)
if test_cases is not None:
test_suite = unittest.TestSuite()
for test_case in test_cases:
if func_name:
if test_case.func_name == func_name:
test_suite.addTest(test_case.test_case)
else:
test_suite.addTest(test_case.test_case)

elif func_name:
test_suite = unittest.TestSuite()
for test_cases in JacTestCheck.test_suite_path.values():
for test_case in test_cases:
if test_case.func_name == func_name:
test_suite.addTest(test_case.test_case)

runner = JacTextTestRunner(max_failures=maxfail, failfast=xit, verbosity=verb)
result = runner.run(JacTestCheck.test_suite)
result = runner.run(test_suite)
if result.wasSuccessful():
print("Passed successfully.")
else:
Expand All @@ -81,9 +124,21 @@ def run_test(xit: bool, maxfail: int | None, verbose: bool) -> None:
)

@staticmethod
def add_test(test_fun: Callable) -> None:
def add_test(filepath: str, func_name: str, test_func: Callable) -> None:
"""Create a new test."""
JacTestCheck.test_suite.addTest(unittest.FunctionTestCase(test_fun))
if filepath and filepath.endswith(".test.jac"):
filepath = filepath[:-9]
elif filepath and filepath.endswith(".jac"):
filepath = filepath[:-4]

if filepath not in JacTestCheck.test_suite_path:
JacTestCheck.test_suite_path[filepath] = []

test_case = unittest.FunctionTestCase(test_func)
JacTestCheck.test_suite_path[filepath].append(
JacTestCheck.TestSuite(test_case=test_case, func_name=func_name)
)
JacTestCheck.test_suite.addTest(test_case)

def __getattr__(self, name: str) -> object:
"""Make convenient check.Equal(...) etc."""
Expand Down
6 changes: 6 additions & 0 deletions jac/jaclang/tests/fixtures/jactest_imported.jac
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@


test this_should_not_run {
print("This test should not run after import.");
assert False;
}
22 changes: 22 additions & 0 deletions jac/jaclang/tests/fixtures/jactest_main.jac
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import jactest_imported;

can fib(n: int) -> int {
if n <= 1 {
return n;
}
return fib(n - 1) + fib(n - 2);
}


test first_two {
print("Testing first 2 fibonacci numbers.");
assert fib(0) == 0;
assert fib(1) == 0;
}

test from_2_to_10 {
print("Testing fibonacci numbers from 2 to 10.");
for i in range(2, 10) {
assert fib(i) == fib(i - 1) + fib(i - 2);
}
}
21 changes: 21 additions & 0 deletions jac/jaclang/tests/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -393,6 +393,27 @@ def test_run_test(self) -> None:
self.assertIn("...F", stderr)
self.assertIn("F.F", stderr)

def test_run_specific_test_only(self) -> None:
"""Test a specific test case."""
process = subprocess.Popen(
[
"jac",
"test",
"-t",
"from_2_to_10",
self.fixture_abs_path("jactest_main.jac"),
],
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True,
)
stdout, stderr = process.communicate()
self.assertIn("Ran 1 test", stderr)
self.assertIn("Testing fibonacci numbers from 2 to 10.", stdout)
self.assertNotIn("Testing first 2 fibonacci numbers.", stdout)
self.assertNotIn("This test should not run after import.", stdout)

def test_graph_coverage(self) -> None:
"""Test for coverage of graph cmd."""
graph_params = set(inspect.signature(cli.dot).parameters.keys())
Expand Down

0 comments on commit d049a8c

Please sign in to comment.