Commit b37f6664 authored by Roman Alifanov's avatar Roman Alifanov

Add @test decorator for built-in testing

- @test("description") decorator marks functions as tests - assert(condition, message) works in both bash and @awk functions - content test command runs all @test functions - Colored output with timing, pass/fail status - DCE skips @test functions in normal compilation mode - Supports combining @test with other decorators (@awk, @retry, etc.)
parent ba1078de
...@@ -152,6 +152,7 @@ result = value | func1 | func2 ...@@ -152,6 +152,7 @@ result = value | func1 | func2
- `@log` — логирование вызовов (выводит в stderr) - `@log` — логирование вызовов (выводит в stderr)
- `@cache(ttl)` — кеширование результатов на ttl секунд - `@cache(ttl)` — кеширование результатов на ttl секунд
- `@awk` — компиляция в AWK (см. ниже) - `@awk` — компиляция в AWK (см. ниже)
- `@test("description")` — пометить функцию как тест (см. ниже)
``` ```
# На функциях # На функциях
...@@ -279,6 +280,49 @@ calc = Calculator () ...@@ -279,6 +280,49 @@ calc = Calculator ()
result = calc.fastSum ("1 2 3 4 5") # 15 result = calc.fastSum ("1 2 3 4 5") # 15
``` ```
### @test — встроенное тестирование
Декоратор `@test` помечает функцию как тест. Тесты запускаются через `content test`.
```
@test ("addition works correctly")
func test_add () {
result = 2 + 3
assert (result == 5, "2 + 3 should equal 5")
}
@test ("string operations work")
func test_strings () {
text = "hello"
assert (text.len () == 5, "length should be 5")
assert (text.upper () == "HELLO", "uppercase should work")
}
```
**assert(condition, message)** — проверяет условие, при неудаче выводит сообщение и помечает тест как failed.
**Комбинация с @awk:**
```
@test ("awk function works")
@awk
func test_awk_sum () {
total = 0
n = "1 2 3".split (" ")
for i in range (1, n + 1) {
total += __split_arr[i]
}
assert (total == 6, "sum should be 6")
}
```
**Вывод тестов:**
- Цветной вывод (PASS/FAIL)
- Время выполнения каждого теста
- Итоговая статистика
**DCE:** В обычном режиме компиляции `@test` функции пропускаются (Dead Code Elimination).
### Лямбды ### Лямбды
``` ```
...@@ -688,6 +732,15 @@ content run lib.ct main.ct -- arg1 arg2 ...@@ -688,6 +732,15 @@ content run lib.ct main.ct -- arg1 arg2
content --build-lib MyLib.ct # -> MyLib.sh content --build-lib MyLib.ct # -> MyLib.sh
``` ```
### test
```bash
content test main.ct # запустить тесты в файле
content test lib.ct main.ct # запустить тесты из нескольких файлов
```
Запускает все функции с декоратором `@test`. Выводит результаты с цветами и временем.
### lint ### lint
```bash ```bash
......
...@@ -180,7 +180,9 @@ class AwkCodegenMixin: ...@@ -180,7 +180,9 @@ class AwkCodegenMixin:
self.emit (f" {self._awk_escape (line)}") self.emit (f" {self._awk_escape (line)}")
self.emit ("}')") self.emit ("}')")
self.emit ('local __awk_rc=$?')
self.emit ('echo "$__CT_RET"') self.emit ('echo "$__CT_RET"')
self.emit ('return $__awk_rc')
self.indent_level -= 1 self.indent_level -= 1
self.emit ("}") self.emit ("}")
self.emit () self.emit ()
...@@ -347,6 +349,13 @@ class AwkCodegenMixin: ...@@ -347,6 +349,13 @@ class AwkCodegenMixin:
emit ("}") emit ("}")
elif isinstance (stmt, ExpressionStmt): elif isinstance (stmt, ExpressionStmt):
if isinstance (stmt.expression, CallExpr) and isinstance (stmt.expression.callee, Identifier):
if stmt.expression.callee.name == "assert":
args = stmt.expression.arguments
cond = self._awk_cond (args[0]) if args else "1"
msg = self._awk_expr (args[1]) if len (args) >= 2 else '"Assertion failed"'
emit (f"if (!({cond})) {{ print {msg} > \"/dev/stderr\"; exit 1 }}")
return
expr = self._awk_expr (stmt.expression) expr = self._awk_expr (stmt.expression)
if expr: if expr:
emit (expr) emit (expr)
......
...@@ -291,6 +291,32 @@ class ClassMixin: ...@@ -291,6 +291,32 @@ class ClassMixin:
def generate_function(self, func: FunctionDecl): def generate_function(self, func: FunctionDecl):
test_decorator = None
other_decorators = []
for dec in func.decorators:
if dec.name == "test":
test_decorator = dec
else:
other_decorators.append(dec)
if test_decorator:
if not self.test_mode:
self.emit(f"# DCE: skipped @test function {func.name}")
return
description = func.name
if test_decorator.arguments:
arg_name, arg_val = test_decorator.arguments[0]
if hasattr(arg_val, 'value'):
description = arg_val.value
self.test_functions.append((func.name, description))
func = FunctionDecl(
name=func.name,
params=func.params,
body=func.body,
decorators=other_decorators,
location=func.location
)
for dec in func.decorators: for dec in func.decorators:
if dec.name == "awk": if dec.name == "awk":
self.generate_awk_function(func) self.generate_awk_function(func)
......
...@@ -42,6 +42,9 @@ class CodeGenerator(StdlibMixin, AwkCodegenMixin, ExprMixin, StmtMixin, ...@@ -42,6 +42,9 @@ class CodeGenerator(StdlibMixin, AwkCodegenMixin, ExprMixin, StmtMixin,
self.functions: Dict[str, FunctionDecl] = {} self.functions: Dict[str, FunctionDecl] = {}
self.inlineable_methods: Dict[tuple, str] = {} self.inlineable_methods: Dict[tuple, str] = {}
self.test_mode: bool = False
self.test_functions: List[tuple] = []
self.array_vars: Set[str] = set() self.array_vars: Set[str] = set()
self.dict_vars: Set[str] = set() self.dict_vars: Set[str] = set()
self.object_vars: Set[str] = set() self.object_vars: Set[str] = set()
...@@ -110,13 +113,17 @@ class CodeGenerator(StdlibMixin, AwkCodegenMixin, ExprMixin, StmtMixin, ...@@ -110,13 +113,17 @@ class CodeGenerator(StdlibMixin, AwkCodegenMixin, ExprMixin, StmtMixin,
"""Generate code for a single program.""" """Generate code for a single program."""
return self.generate_multi([program]) return self.generate_multi([program])
def generate_multi(self, programs: list, dce: bool = True) -> str: def generate_multi(self, programs: list, dce: bool = True, test_mode: bool = False) -> str:
"""Generate code for multiple programs (multi-file compilation). """Generate code for multiple programs (multi-file compilation).
Args: Args:
programs: List of Program AST nodes programs: List of Program AST nodes
dce: Enable Dead Code Elimination (default True) dce: Enable Dead Code Elimination (default True)
test_mode: Generate test runner instead of normal execution
""" """
self.test_mode = test_mode
self.test_functions = []
self.emit_raw("#!/usr/bin/env bash") self.emit_raw("#!/usr/bin/env bash")
self.emit_raw("# Generated by ContenT compiler") self.emit_raw("# Generated by ContenT compiler")
self.emit_raw("set -euo pipefail") self.emit_raw("set -euo pipefail")
...@@ -124,7 +131,9 @@ class CodeGenerator(StdlibMixin, AwkCodegenMixin, ExprMixin, StmtMixin, ...@@ -124,7 +131,9 @@ class CodeGenerator(StdlibMixin, AwkCodegenMixin, ExprMixin, StmtMixin,
if dce: if dce:
analyzer = UsageAnalyzer() analyzer = UsageAnalyzer()
used_categories = analyzer.analyze(programs) used_categories = analyzer.analyze(programs, test_mode=test_mode)
if test_mode:
used_categories.add('test')
self.emit_stdlib(used_categories) self.emit_stdlib(used_categories)
self.used_classes = analyzer.get_used_classes() self.used_classes = analyzer.get_used_classes()
self.used_methods = analyzer.used_methods self.used_methods = analyzer.used_methods
...@@ -147,4 +156,44 @@ class CodeGenerator(StdlibMixin, AwkCodegenMixin, ExprMixin, StmtMixin, ...@@ -147,4 +156,44 @@ class CodeGenerator(StdlibMixin, AwkCodegenMixin, ExprMixin, StmtMixin,
for stmt in program.statements: for stmt in program.statements:
self.generate_statement(stmt) self.generate_statement(stmt)
if test_mode and self.test_functions:
self._generate_test_runner()
return "\n".join(self.output) return "\n".join(self.output)
def _generate_test_runner(self):
"""Generate test runner that executes all @test functions."""
self.emit()
self.emit("# === Test Runner ===")
self.emit(f'echo "Running {len(self.test_functions)} tests..."')
self.emit('echo')
self.emit('__ct_run_tests () {')
self.indent_level += 1
self.emit('local __test_failed=0')
for func_name, description in self.test_functions:
escaped_desc = description.replace('"', '\\"')
self.emit(f'__ct_test_start "{escaped_desc}"')
self.emit('local __prev_failed=$__ct_test_failed')
self.emit(f'if {func_name}; then')
self.indent_level += 1
self.emit('__ct_test_pass')
self.indent_level -= 1
self.emit('else')
self.indent_level += 1
self.emit('if [[ $__ct_test_failed -eq $__prev_failed ]]; then')
self.indent_level += 1
self.emit('__ct_test_fail ""')
self.indent_level -= 1
self.emit('fi')
self.emit('__test_failed=1')
self.indent_level -= 1
self.emit('fi')
self.emit()
self.emit('__ct_test_summary')
self.indent_level -= 1
self.emit('}')
self.emit()
self.emit('__ct_run_tests')
...@@ -15,6 +15,7 @@ class UsageAnalyzer: ...@@ -15,6 +15,7 @@ class UsageAnalyzer:
self.used: set = set() self.used: set = set()
self.has_classes = False self.has_classes = False
self.has_awk = False self.has_awk = False
self.test_mode = False
self.defined_classes: dict = {} self.defined_classes: dict = {}
self.used_classes: set = set() self.used_classes: set = set()
self.used_methods: dict = {} self.used_methods: dict = {}
...@@ -24,8 +25,9 @@ class UsageAnalyzer: ...@@ -24,8 +25,9 @@ class UsageAnalyzer:
self.current_method_name: str = None self.current_method_name: str = None
self.method_calls: dict = {} self.method_calls: dict = {}
def analyze(self, programs: list) -> set: def analyze(self, programs: list, test_mode: bool = False) -> set:
self.used = {'core'} self.used = {'core'}
self.test_mode = test_mode
for program in programs: for program in programs:
for stmt in program.statements: for stmt in program.statements:
...@@ -130,6 +132,10 @@ class UsageAnalyzer: ...@@ -130,6 +132,10 @@ class UsageAnalyzer:
self.current_class_name = None self.current_class_name = None
elif isinstance(stmt, FunctionDecl): elif isinstance(stmt, FunctionDecl):
is_test = any(dec.name == 'test' for dec in stmt.decorators)
if is_test and not self.test_mode:
return
if stmt.decorators: if stmt.decorators:
for dec in stmt.decorators: for dec in stmt.decorators:
if dec.name == 'awk': if dec.name == 'awk':
......
...@@ -20,7 +20,7 @@ STR_METHODS = { ...@@ -20,7 +20,7 @@ STR_METHODS = {
} }
BUILTIN_NAMESPACES = {"fs", "http", "json", "logger", "regex", "args", "shell", "time", "math"} BUILTIN_NAMESPACES = {"fs", "http", "json", "logger", "regex", "args", "shell", "time", "math"}
BUILTIN_FUNCS = {"print", "exit", "len", "range", "ngrep", "is_number", "is_empty", "chr", "ord"} BUILTIN_FUNCS = {"print", "exit", "len", "range", "ngrep", "is_number", "is_empty", "chr", "ord", "assert", "assert_eq"}
class DispatchMixin: class DispatchMixin:
...@@ -357,6 +357,10 @@ class DispatchMixin: ...@@ -357,6 +357,10 @@ class DispatchMixin:
return return
if isinstance(expr, CallExpr): if isinstance(expr, CallExpr):
if isinstance(expr.callee, Identifier) and expr.callee.name == "assert":
self._generate_assert_stmt(expr)
return
if isinstance(expr.callee, MemberAccess): if isinstance(expr.callee, MemberAccess):
if self._handle_field_method_call(expr): if self._handle_field_method_call(expr):
return return
...@@ -382,6 +386,22 @@ class DispatchMixin: ...@@ -382,6 +386,22 @@ class DispatchMixin:
if result: if result:
self.emit(result) self.emit(result)
def _generate_assert_stmt(self, expr: CallExpr):
"""Generate assert statement inline."""
condition_expr = expr.arguments[0] if expr.arguments else None
message = "Assertion failed"
if len(expr.arguments) >= 2:
msg_expr = expr.arguments[1]
if hasattr(msg_expr, 'value'):
message = msg_expr.value
else:
message = self.generate_expr(msg_expr)
if condition_expr:
cond = self.generate_condition(condition_expr)
escaped_msg = message.replace('"', '\\"')
self.emit(f'if {cond}; then :; else __ct_test_fail "{escaped_msg}"; return 1; fi')
def _handle_field_method_call(self, expr: CallExpr) -> bool: def _handle_field_method_call(self, expr: CallExpr) -> bool:
"""Handle this.field.method() or var.field.method(). Returns True if handled.""" """Handle this.field.method() or var.field.method(). Returns True if handled."""
callee = expr.callee callee = expr.callee
...@@ -506,6 +526,12 @@ class DispatchMixin: ...@@ -506,6 +526,12 @@ class DispatchMixin:
return f'__ct_str_chr {args_str}' return f'__ct_str_chr {args_str}'
elif name == "ord": elif name == "ord":
return f'__ct_str_ord {args_str}' return f'__ct_str_ord {args_str}'
elif name == "assert":
if len(args) >= 2:
return f'__ct_assert "{args[0]}" "{args[1]}"'
return f'__ct_assert "{args[0]}"'
elif name == "assert_eq":
return f'__ct_assert_eq {args_str}'
else: else:
return f'{name} {args_str}' return f'{name} {args_str}'
......
...@@ -184,6 +184,68 @@ def cmd_run (args): ...@@ -184,6 +184,68 @@ def cmd_run (args):
return 1 return 1
def cmd_test (args):
"""Test command - run @test functions"""
source_paths = args.sources
if source_paths == ["."]:
source_paths = find_ct_files (".")
if not source_paths:
print ("Error: No .ct files found in current directory", file=sys.stderr)
return 1
for source_path in source_paths:
if not source_path.endswith (".ct"):
print (f"Error: Source file must have .ct extension: {source_path}", file=sys.stderr)
return 1
asts = []
for source_path in source_paths:
ast = parse_file (source_path)
if ast is None:
return 1
asts.append (ast)
codegen = CodeGenerator ()
output = codegen.generate_multi (asts, test_mode=True)
if codegen.errors.has_errors ():
codegen.errors.print_errors ()
return 1
if not codegen.test_functions:
print ("No @test functions found", file=sys.stderr)
return 1
try:
with tempfile.NamedTemporaryFile (
mode="w",
suffix=".sh",
delete=False,
encoding="utf-8"
) as f:
f.write (output)
temp_path = f.name
os.chmod (temp_path, 0o755)
try:
result = subprocess.run (
["bash", temp_path],
check=False
)
return result.returncode
finally:
try:
os.unlink (temp_path)
except:
pass
except Exception as e:
print (f"Error: {e}", file=sys.stderr)
return 1
def cmd_build_lib (args): def cmd_build_lib (args):
"""Build library command""" """Build library command"""
source_path = args.source source_path = args.source
...@@ -244,6 +306,9 @@ def main (): ...@@ -244,6 +306,9 @@ def main ():
lib_parser = subparsers.add_parser ("build-lib", help="Build a library") lib_parser = subparsers.add_parser ("build-lib", help="Build a library")
lib_parser.add_argument ("source", help="Source file (.ct)") lib_parser.add_argument ("source", help="Source file (.ct)")
test_parser = subparsers.add_parser ("test", help="Run @test functions")
test_parser.add_argument ("sources", nargs="+", help="Source files (.ct)")
parser.add_argument ("--build-lib", metavar="FILE", help="Build a library") parser.add_argument ("--build-lib", metavar="FILE", help="Build a library")
args = parser.parse_args () args = parser.parse_args ()
...@@ -259,6 +324,8 @@ def main (): ...@@ -259,6 +324,8 @@ def main ():
return cmd_run (args) return cmd_run (args)
elif args.command == "build-lib": elif args.command == "build-lib":
return cmd_build_lib (args) return cmd_build_lib (args)
elif args.command == "test":
return cmd_test (args)
else: else:
parser.print_help () parser.print_help ()
return 0 return 0
......
...@@ -64,6 +64,8 @@ class StdlibMixin: ...@@ -64,6 +64,8 @@ class StdlibMixin:
self._emit_dict () self._emit_dict ()
if 'misc' in used_categories or 'time' in used_categories: if 'misc' in used_categories or 'time' in used_categories:
self._emit_misc () self._emit_misc ()
if 'test' in used_categories:
self._emit_test ()
self.emit ("# === End Standard Library ===") self.emit ("# === End Standard Library ===")
self.emit () self.emit ()
...@@ -456,3 +458,115 @@ class StdlibMixin: ...@@ -456,3 +458,115 @@ class StdlibMixin:
self.emit ("__CT_NL=$'\\n'") self.emit ("__CT_NL=$'\\n'")
self.emit () self.emit ()
def _emit_test (self):
"""Test framework functions: assert, test runner."""
self.emit ("# Test framework")
self.emit ("declare -g __ct_test_passed=0")
self.emit ("declare -g __ct_test_failed=0")
self.emit ("declare -g __ct_test_current=''")
self.emit ("declare -g __ct_test_start_time=0")
self.emit ()
self.emit ("__ct_test_colors () {")
self.indent_level += 1
self.emit ('if [[ -t 1 ]]; then')
self.indent_level += 1
self.emit ('__CT_GREEN="\\033[32m"')
self.emit ('__CT_RED="\\033[31m"')
self.emit ('__CT_YELLOW="\\033[33m"')
self.emit ('__CT_CYAN="\\033[36m"')
self.emit ('__CT_RESET="\\033[0m"')
self.indent_level -= 1
self.emit ('else')
self.indent_level += 1
self.emit ('__CT_GREEN="" __CT_RED="" __CT_YELLOW="" __CT_CYAN="" __CT_RESET=""')
self.indent_level -= 1
self.emit ('fi')
self.indent_level -= 1
self.emit ("}")
self.emit ("__ct_test_colors")
self.emit ()
self.emit ("__ct_test_start () {")
self.indent_level += 1
self.emit ('__ct_test_current="$1"')
self.emit ('__ct_test_start_time=$(date +%s%3N)')
self.emit ('printf "${__CT_CYAN}RUN${__CT_RESET} %s\\n" "$1"')
self.indent_level -= 1
self.emit ("}")
self.emit ()
self.emit ("__ct_test_pass () {")
self.indent_level += 1
self.emit ('local elapsed=$(($(date +%s%3N) - __ct_test_start_time))')
self.emit ('printf "${__CT_GREEN}PASS${__CT_RESET} %s ${__CT_YELLOW}(%dms)${__CT_RESET}\\n" "$__ct_test_current" "$elapsed"')
self.emit ('((__ct_test_passed++)) || true')
self.indent_level -= 1
self.emit ("}")
self.emit ()
self.emit ("__ct_test_fail () {")
self.indent_level += 1
self.emit ('local msg="$1"')
self.emit ('local elapsed=$(($(date +%s%3N) - __ct_test_start_time))')
self.emit ('printf "${__CT_RED}FAIL${__CT_RESET} %s ${__CT_YELLOW}(%dms)${__CT_RESET}\\n" "$__ct_test_current" "$elapsed"')
self.emit ('if [[ -n "$msg" ]]; then')
self.indent_level += 1
self.emit ('printf " ${__CT_RED}%s${__CT_RESET}\\n" "$msg"')
self.indent_level -= 1
self.emit ('fi')
self.emit ('((__ct_test_failed++)) || true')
self.indent_level -= 1
self.emit ("}")
self.emit ()
self.emit ("__ct_assert () {")
self.indent_level += 1
self.emit ('local condition="$1"')
self.emit ('local msg="${2:-Assertion failed}"')
self.emit ('if eval "$condition"; then')
self.indent_level += 1
self.emit ('return 0')
self.indent_level -= 1
self.emit ('else')
self.indent_level += 1
self.emit ('__ct_test_fail "$msg"')
self.emit ('return 1')
self.indent_level -= 1
self.emit ('fi')
self.indent_level -= 1
self.emit ("}")
self.emit ()
self.emit ("__ct_assert_eq () {")
self.indent_level += 1
self.emit ('local actual="$1"')
self.emit ('local expected="$2"')
self.emit ("if [[ \"$actual\" != \"$expected\" ]]; then")
self.indent_level += 1
self.emit ("__ct_test_fail \"Expected '$expected' but got '$actual'\"")
self.emit ('return 1')
self.indent_level -= 1
self.emit ('fi')
self.indent_level -= 1
self.emit ("}")
self.emit ()
self.emit ("__ct_test_summary () {")
self.indent_level += 1
self.emit ('echo')
self.emit ('local total=$((__ct_test_passed + __ct_test_failed))')
self.emit ('if [[ $__ct_test_failed -eq 0 ]]; then')
self.indent_level += 1
self.emit ('printf "${__CT_GREEN}All %d tests passed${__CT_RESET}\\n" "$total"')
self.indent_level -= 1
self.emit ('else')
self.indent_level += 1
self.emit ('printf "${__CT_RED}%d of %d tests failed${__CT_RESET}\\n" "$__ct_test_failed" "$total"')
self.indent_level -= 1
self.emit ('fi')
self.emit ('return $__ct_test_failed')
self.indent_level -= 1
self.emit ("}")
self.emit ()
func add (a, b) {
return a + b
}
func multiply (a, b) {
return a * b
}
@test ("addition works correctly")
func test_add () {
result = add (2, 3)
assert (result == 5, "2 + 3 should equal 5")
}
@test ("multiplication works correctly")
func test_multiply () {
result = multiply (4, 5)
assert (result == 20, "4 * 5 should equal 20")
}
@test ("string operations work")
func test_strings () {
text = "hello"
assert (text.len () == 5, "length should be 5")
assert (text.upper () == "HELLO", "uppercase should work")
}
@test ("array operations work")
func test_arrays () {
arr = [1, 2, 3]
assert (arr.len () == 3, "array length should be 3")
arr.push (4)
assert (arr.len () == 4, "array length after push should be 4")
}
@awk
func fast_sum (text) {
total = 0
n = text.split (" ")
for i in range (1, n + 1) {
total += __split_arr[i]
}
return total
}
@test ("awk functions work")
func test_awk_sum () {
result = fast_sum ("1 2 3 4 5")
assert (result == 15, "sum of 1+2+3+4+5 should be 15")
}
...@@ -406,3 +406,114 @@ greet () ...@@ -406,3 +406,114 @@ greet ()
''') ''')
assert code == 0 assert code == 0
assert "greet" in stdout assert "greet" in stdout
def run_ct_test(source: str) -> tuple[int, str, str]:
with tempfile.NamedTemporaryFile(mode='w', suffix='.ct', delete=False) as f:
f.write(source)
f.flush()
ct_file = f.name
try:
result = subprocess.run(
['python3', 'content', 'test', ct_file],
capture_output=True,
text=True,
timeout=10
)
return result.returncode, result.stdout, result.stderr
finally:
os.unlink(ct_file)
class TestTestDecorator:
def test_passing_test(self):
code, stdout, stderr = run_ct_test('''
@test ("simple test passes")
func test_simple () {
x = 5
assert (x == 5, "x should be 5")
}
''')
assert code == 0
assert "PASS" in stdout
assert "simple test passes" in stdout
def test_failing_test(self):
code, stdout, stderr = run_ct_test('''
@test ("simple test fails")
func test_simple () {
x = 5
assert (x == 10, "x should be 10")
}
''')
assert code == 1
assert "FAIL" in stdout
assert "x should be 10" in stdout
def test_multiple_tests(self):
code, stdout, stderr = run_ct_test('''
@test ("first test")
func test_one () {
assert (1 == 1, "math works")
}
@test ("second test")
func test_two () {
assert (2 == 2, "math still works")
}
''')
assert code == 0
assert "2 tests passed" in stdout
def test_with_regular_functions(self):
code, stdout, stderr = run_ct_test('''
func add (a, b) {
return a + b
}
@test ("add function works")
func test_add () {
result = add (2, 3)
assert (result == 5, "2 + 3 should be 5")
}
''')
assert code == 0
assert "PASS" in stdout
def test_awk_assert(self):
code, stdout, stderr = run_ct_test('''
@test ("awk assert works")
@awk
func test_awk () {
x = 5
assert (x == 5, "x should be 5")
}
''')
assert code == 0
assert "PASS" in stdout
def test_awk_assert_fails(self):
code, stdout, stderr = run_ct_test('''
@test ("awk assert fails")
@awk
func test_awk () {
x = 5
assert (x == 10, "x should be 10")
}
''')
assert code == 1
assert "FAIL" in stdout
def test_dce_skips_test_in_normal_mode(self):
code, stdout, stderr = compile_ct('''
@test ("this should be skipped")
func test_skipped () {
assert (1 == 1, "ok")
}
print ("hello")
''')
assert code == 0
assert "DCE: skipped @test" in stdout
assert "test_skipped" not in stdout or "skipped" in stdout
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment