small fixes, tests
Authored by
mfwolffe <wolffemf@dukes.jmu.edu>
- SHA
bb1c5f49d6c5a45e47239a2ccf825ecb121f1e7f- Parents
-
d6fd7dd - Tree
984c0f5
bb1c5f4
bb1c5f49d6c5a45e47239a2ccf825ecb121f1e7fd6fd7dd
984c0f5| Status | File | + | - |
|---|---|---|---|
| A |
.tool-versions
|
1 | 0 |
| M |
Makefile
|
73 | 8 |
| M |
src/fortbite_evaluator_m.f90
|
8 | 8 |
| M |
src/fortbite_parser_m.f90
|
16 | 16 |
| M |
src/fortbite_types_m.f90
|
13 | 2 |
| A |
test_results/arithmetic.log
|
26 | 0 |
| A |
tests/fixtures/basic_arithmetic.expected
|
5 | 0 |
| A |
tests/fixtures/basic_arithmetic.fb
|
6 | 0 |
| A |
tests/integration/test_expressions.f90
|
190 | 0 |
| A |
tests/run_tests.sh
|
131 | 0 |
| A |
tests/test_framework.f90
|
179 | 0 |
| A |
tests/unit/test_arithmetic.f90
|
145 | 0 |
| A |
tests/unit/test_functions.f90
|
199 | 0 |
.tool-versionsadded@@ -0,0 +1,1 @@ | |||
| 1 | +nodejs 24.9.0 | ||
Makefilemodified@@ -56,8 +56,8 @@ $(BUILDDIR)/fortbite_arithmetic_m.o: $(BUILDDIR)/fortbite_precision_m.o $(BUILDD | |||
| 56 | $(BUILDDIR)/fortbite_ast_m.o: $(BUILDDIR)/fortbite_types_m.o | 56 | $(BUILDDIR)/fortbite_ast_m.o: $(BUILDDIR)/fortbite_types_m.o |
| 57 | $(BUILDDIR)/fortbite_lexer_m.o: $(BUILDDIR)/fortbite_types_m.o | 57 | $(BUILDDIR)/fortbite_lexer_m.o: $(BUILDDIR)/fortbite_types_m.o |
| 58 | $(BUILDDIR)/fortbite_parser_m.o: $(BUILDDIR)/fortbite_types_m.o $(BUILDDIR)/fortbite_ast_m.o | 58 | $(BUILDDIR)/fortbite_parser_m.o: $(BUILDDIR)/fortbite_types_m.o $(BUILDDIR)/fortbite_ast_m.o |
| 59 | -$(BUILDDIR)/fortbite_evaluator_m.o: $(BUILDDIR)/fortbite_types_m.o $(BUILDDIR)/fortbite_ast_m.o $(BUILDDIR)/fortbite_arithmetic_m.o $(BUILDDIR)/fortbite_functions_m.o | 59 | +$(BUILDDIR)/fortbite_evaluator_m.o: $(BUILDDIR)/fortbite_types_m.o $(BUILDDIR)/fortbite_ast_m.o $(BUILDDIR)/fortbite_arithmetic_m.o $(BUILDDIR)/fortbite_functions_m.o $(BUILDDIR)/fortbite_matrix_m.o |
| 60 | -$(BUILDDIR)/fortbite_io_m.o: $(BUILDDIR)/fortbite_precision_m.o $(BUILDDIR)/fortbite_types_m.o | 60 | +$(BUILDDIR)/fortbite_io_m.o: $(BUILDDIR)/fortbite_precision_m.o $(BUILDDIR)/fortbite_types_m.o $(BUILDDIR)/fortbite_lexer_m.o $(BUILDDIR)/fortbite_parser_m.o $(BUILDDIR)/fortbite_evaluator_m.o $(BUILDDIR)/fortbite_ast_m.o |
| 61 | $(BUILDDIR)/fortbite.o: $(BUILDDIR)/fortbite_precision_m.o $(BUILDDIR)/fortbite_types_m.o $(BUILDDIR)/fortbite_io_m.o | 61 | $(BUILDDIR)/fortbite.o: $(BUILDDIR)/fortbite_precision_m.o $(BUILDDIR)/fortbite_types_m.o $(BUILDDIR)/fortbite_io_m.o |
| 62 | 62 | ||
| 63 | # Clean up | 63 | # Clean up |
@@ -72,13 +72,78 @@ run: $(TARGET) | |||
| 72 | install: $(TARGET) | 72 | install: $(TARGET) |
| 73 | cp $(TARGET) /usr/local/bin/fortbite | 73 | cp $(TARGET) /usr/local/bin/fortbite |
| 74 | 74 | ||
| 75 | +# Test compilation | ||
| 76 | +TEST_FRAMEWORK = tests/test_framework.f90 | ||
| 77 | +TEST_SOURCES = tests/unit/test_arithmetic.f90 \ | ||
| 78 | + tests/unit/test_functions.f90 \ | ||
| 79 | + tests/integration/test_expressions.f90 | ||
| 80 | + | ||
| 81 | +TEST_OBJECTS = $(TEST_SOURCES:tests/%.f90=$(BUILDDIR)/tests/%.o) \ | ||
| 82 | + $(BUILDDIR)/tests/test_framework.o | ||
| 83 | + | ||
| 84 | +TEST_EXECUTABLES = $(TEST_SOURCES:tests/%.f90=$(BUILDDIR)/tests/%) | ||
| 85 | + | ||
| 86 | +# Build test framework | ||
| 87 | +$(BUILDDIR)/tests/test_framework.o: $(TEST_FRAMEWORK) directories | ||
| 88 | + @mkdir -p $(BUILDDIR)/tests/unit $(BUILDDIR)/tests/integration | ||
| 89 | + $(FC) $(FFLAGS) -J$(MODDIR) -c $< -o $@ | ||
| 90 | + | ||
| 91 | +# Library objects (all except main program) | ||
| 92 | +LIB_OBJECTS = $(filter-out $(BUILDDIR)/fortbite.o, $(OBJECTS)) | ||
| 93 | + | ||
| 94 | +# Build test executables | ||
| 95 | +$(BUILDDIR)/tests/unit/%.o: tests/unit/%.f90 $(BUILDDIR)/tests/test_framework.o $(LIB_OBJECTS) | ||
| 96 | + $(FC) $(FFLAGS) -J$(MODDIR) -I$(MODDIR) -c $< -o $@ | ||
| 97 | + | ||
| 98 | +$(BUILDDIR)/tests/integration/%.o: tests/integration/%.f90 $(BUILDDIR)/tests/test_framework.o $(LIB_OBJECTS) | ||
| 99 | + $(FC) $(FFLAGS) -J$(MODDIR) -I$(MODDIR) -c $< -o $@ | ||
| 100 | + | ||
| 101 | +$(BUILDDIR)/tests/unit/%: $(BUILDDIR)/tests/unit/%.o $(BUILDDIR)/tests/test_framework.o $(LIB_OBJECTS) | ||
| 102 | + $(FC) $(LDFLAGS) -J$(MODDIR) -o $@ $^ | ||
| 103 | + | ||
| 104 | +$(BUILDDIR)/tests/integration/%: $(BUILDDIR)/tests/integration/%.o $(BUILDDIR)/tests/test_framework.o $(LIB_OBJECTS) | ||
| 105 | + $(FC) $(LDFLAGS) -J$(MODDIR) -o $@ $^ | ||
| 106 | + | ||
| 107 | +# Test targets | ||
| 108 | +test-build: directories $(TARGET) $(TEST_EXECUTABLES) | ||
| 109 | + @echo "Test build complete!" | ||
| 110 | + | ||
| 111 | +test: test-build | ||
| 112 | + @./tests/run_tests.sh --no-build | ||
| 113 | + | ||
| 114 | +test-unit: test-build | ||
| 115 | + @echo "Running unit tests..." | ||
| 116 | + @$(BUILDDIR)/tests/unit/test_arithmetic || true | ||
| 117 | + @$(BUILDDIR)/tests/unit/test_functions || true | ||
| 118 | + | ||
| 119 | +test-integration: test-build | ||
| 120 | + @echo "Running integration tests..." | ||
| 121 | + @$(BUILDDIR)/tests/integration/test_expressions || true | ||
| 122 | + | ||
| 123 | +test-verbose: test-build | ||
| 124 | + @for test in $(TEST_EXECUTABLES); do \ | ||
| 125 | + echo "=== Running $$test ==="; \ | ||
| 126 | + $$test || true; \ | ||
| 127 | + echo; \ | ||
| 128 | + done | ||
| 129 | + | ||
| 130 | +# Clean including tests | ||
| 131 | +clean-all: clean | ||
| 132 | + rm -rf test_results | ||
| 133 | + | ||
| 75 | # Help | 134 | # Help |
| 76 | help: | 135 | help: |
| 77 | @echo "FORTBITE Makefile targets:" | 136 | @echo "FORTBITE Makefile targets:" |
| 78 | - @echo " all - Build the executable (default)" | 137 | + @echo " all - Build the executable (default)" |
| 79 | - @echo " clean - Remove build files" | 138 | + @echo " clean - Remove build files" |
| 80 | - @echo " run - Build and run FORTBITE" | 139 | + @echo " clean-all - Remove build files and test results" |
| 81 | - @echo " install - Install to /usr/local/bin" | 140 | + @echo " run - Build and run FORTBITE" |
| 82 | - @echo " help - Show this help message" | 141 | + @echo " install - Install to /usr/local/bin" |
| 142 | + @echo " test - Run all tests" | ||
| 143 | + @echo " test-unit - Run unit tests only" | ||
| 144 | + @echo " test-integration - Run integration tests only" | ||
| 145 | + @echo " test-verbose - Run all tests with full output" | ||
| 146 | + @echo " test-build - Build test executables only" | ||
| 147 | + @echo " help - Show this help message" | ||
| 83 | 148 | ||
| 84 | -.PHONY: all clean run install help directories | 149 | +.PHONY: all clean clean-all run install help directories test test-unit test-integration test-verbose test-build |
src/fortbite_evaluator_m.f90modified@@ -654,21 +654,21 @@ contains | |||
| 654 | type(evaluation_context_t), intent(inout) :: context | 654 | type(evaluation_context_t), intent(inout) :: context |
| 655 | type(evaluation_error_t), intent(out) :: error | 655 | type(evaluation_error_t), intent(out) :: error |
| 656 | type(value_t) :: value | 656 | type(value_t) :: value |
| 657 | - | 657 | + |
| 658 | type(evaluation_error_t) :: expr_error | 658 | type(evaluation_error_t) :: expr_error |
| 659 | - | 659 | + |
| 660 | error%has_error = .false. | 660 | error%has_error = .false. |
| 661 | - | 661 | + |
| 662 | - ! For now, just evaluate the expression (ignore precision specification) | 662 | + ! Evaluate the expression |
| 663 | - ! TODO: Implement actual precision control in Phase 3 | ||
| 664 | value = evaluate_expression(node%expression, context, expr_error) | 663 | value = evaluate_expression(node%expression, context, expr_error) |
| 665 | if (expr_error%has_error) then | 664 | if (expr_error%has_error) then |
| 666 | error = expr_error | 665 | error = expr_error |
| 667 | return | 666 | return |
| 668 | end if | 667 | end if |
| 669 | - | 668 | + |
| 670 | - ! Could modify precision here based on node%precision_digits | 669 | + ! Store the requested precision in the value |
| 671 | - ! For now, just return the value as-is | 670 | + ! We'll use the precision_kind field to store the number of digits |
| 671 | + value%precision_kind = node%precision_digits | ||
| 672 | end function evaluate_precision_spec | 672 | end function evaluate_precision_spec |
| 673 | 673 | ||
| 674 | !> Evaluate a matrix literal | 674 | !> Evaluate a matrix literal |
src/fortbite_parser_m.f90modified@@ -113,43 +113,43 @@ contains | |||
| 113 | end function parse_assignment | 113 | end function parse_assignment |
| 114 | 114 | ||
| 115 | !> Parse logical OR expressions (placeholder for future boolean logic) | 115 | !> Parse logical OR expressions (placeholder for future boolean logic) |
| 116 | - function parse_logical_or(parser) result(node) | 116 | + recursive function parse_logical_or(parser) result(node) |
| 117 | type(parser_state_t), intent(inout) :: parser | 117 | type(parser_state_t), intent(inout) :: parser |
| 118 | type(ast_node_t), pointer :: node | 118 | type(ast_node_t), pointer :: node |
| 119 | - | 119 | + |
| 120 | ! For now, just pass through to the next level | 120 | ! For now, just pass through to the next level |
| 121 | node => parse_logical_and(parser) | 121 | node => parse_logical_and(parser) |
| 122 | end function parse_logical_or | 122 | end function parse_logical_or |
| 123 | - | 123 | + |
| 124 | !> Parse logical AND expressions (placeholder for future boolean logic) | 124 | !> Parse logical AND expressions (placeholder for future boolean logic) |
| 125 | - function parse_logical_and(parser) result(node) | 125 | + recursive function parse_logical_and(parser) result(node) |
| 126 | type(parser_state_t), intent(inout) :: parser | 126 | type(parser_state_t), intent(inout) :: parser |
| 127 | type(ast_node_t), pointer :: node | 127 | type(ast_node_t), pointer :: node |
| 128 | - | 128 | + |
| 129 | ! For now, just pass through to the next level | 129 | ! For now, just pass through to the next level |
| 130 | node => parse_equality(parser) | 130 | node => parse_equality(parser) |
| 131 | end function parse_logical_and | 131 | end function parse_logical_and |
| 132 | - | 132 | + |
| 133 | !> Parse equality expressions (placeholder for future comparisons) | 133 | !> Parse equality expressions (placeholder for future comparisons) |
| 134 | - function parse_equality(parser) result(node) | 134 | + recursive function parse_equality(parser) result(node) |
| 135 | type(parser_state_t), intent(inout) :: parser | 135 | type(parser_state_t), intent(inout) :: parser |
| 136 | type(ast_node_t), pointer :: node | 136 | type(ast_node_t), pointer :: node |
| 137 | - | 137 | + |
| 138 | ! For now, just pass through to the next level | 138 | ! For now, just pass through to the next level |
| 139 | node => parse_relational(parser) | 139 | node => parse_relational(parser) |
| 140 | end function parse_equality | 140 | end function parse_equality |
| 141 | - | 141 | + |
| 142 | !> Parse relational expressions (placeholder for future comparisons) | 142 | !> Parse relational expressions (placeholder for future comparisons) |
| 143 | - function parse_relational(parser) result(node) | 143 | + recursive function parse_relational(parser) result(node) |
| 144 | type(parser_state_t), intent(inout) :: parser | 144 | type(parser_state_t), intent(inout) :: parser |
| 145 | type(ast_node_t), pointer :: node | 145 | type(ast_node_t), pointer :: node |
| 146 | - | 146 | + |
| 147 | ! For now, just pass through to the next level | 147 | ! For now, just pass through to the next level |
| 148 | node => parse_additive(parser) | 148 | node => parse_additive(parser) |
| 149 | end function parse_relational | 149 | end function parse_relational |
| 150 | - | 150 | + |
| 151 | !> Parse additive expressions (+ and -) | 151 | !> Parse additive expressions (+ and -) |
| 152 | - function parse_additive(parser) result(node) | 152 | + recursive function parse_additive(parser) result(node) |
| 153 | type(parser_state_t), intent(inout) :: parser | 153 | type(parser_state_t), intent(inout) :: parser |
| 154 | type(ast_node_t), pointer :: node | 154 | type(ast_node_t), pointer :: node |
| 155 | 155 | ||
@@ -178,7 +178,7 @@ contains | |||
| 178 | end function parse_additive | 178 | end function parse_additive |
| 179 | 179 | ||
| 180 | !> Parse multiplicative expressions (*, /, mod) | 180 | !> Parse multiplicative expressions (*, /, mod) |
| 181 | - function parse_multiplicative(parser) result(node) | 181 | + recursive function parse_multiplicative(parser) result(node) |
| 182 | type(parser_state_t), intent(inout) :: parser | 182 | type(parser_state_t), intent(inout) :: parser |
| 183 | type(ast_node_t), pointer :: node | 183 | type(ast_node_t), pointer :: node |
| 184 | 184 | ||
@@ -264,7 +264,7 @@ contains | |||
| 264 | end function parse_unary | 264 | end function parse_unary |
| 265 | 265 | ||
| 266 | !> Parse postfix expressions (precision specifiers) | 266 | !> Parse postfix expressions (precision specifiers) |
| 267 | - function parse_postfix(parser) result(node) | 267 | + recursive function parse_postfix(parser) result(node) |
| 268 | type(parser_state_t), intent(inout) :: parser | 268 | type(parser_state_t), intent(inout) :: parser |
| 269 | type(ast_node_t), pointer :: node | 269 | type(ast_node_t), pointer :: node |
| 270 | 270 | ||
@@ -287,7 +287,7 @@ contains | |||
| 287 | end function parse_postfix | 287 | end function parse_postfix |
| 288 | 288 | ||
| 289 | !> Parse primary expressions (literals, identifiers, parentheses, functions) | 289 | !> Parse primary expressions (literals, identifiers, parentheses, functions) |
| 290 | - function parse_primary(parser) result(node) | 290 | + recursive function parse_primary(parser) result(node) |
| 291 | type(parser_state_t), intent(inout) :: parser | 291 | type(parser_state_t), intent(inout) :: parser |
| 292 | type(ast_node_t), pointer :: node | 292 | type(ast_node_t), pointer :: node |
| 293 | 293 | ||
src/fortbite_types_m.f90modified@@ -266,10 +266,21 @@ contains | |||
| 266 | !> Print a value to standard output | 266 | !> Print a value to standard output |
| 267 | subroutine print_value(value) | 267 | subroutine print_value(value) |
| 268 | type(value_t), intent(in) :: value | 268 | type(value_t), intent(in) :: value |
| 269 | - | 269 | + character(len=100) :: fmt_str |
| 270 | + integer :: precision_digits | ||
| 271 | + | ||
| 270 | select case (value%value_type) | 272 | select case (value%value_type) |
| 271 | case (VALUE_SCALAR) | 273 | case (VALUE_SCALAR) |
| 272 | - write(*, '(G0)') value%scalar_val | 274 | + ! Use precision_kind to determine output format |
| 275 | + ! If precision_kind < 100, it's the requested precision digits | ||
| 276 | + ! Otherwise it's a kind parameter (like real64 = 8) | ||
| 277 | + if (value%precision_kind > 0 .and. value%precision_kind < 100) then | ||
| 278 | + precision_digits = value%precision_kind | ||
| 279 | + write(fmt_str, '(A,I0,A,I0,A)') '(F0.', precision_digits, ')' | ||
| 280 | + write(*, fmt_str) value%scalar_val | ||
| 281 | + else | ||
| 282 | + write(*, '(G0)') value%scalar_val | ||
| 283 | + end if | ||
| 273 | case (VALUE_COMPLEX) | 284 | case (VALUE_COMPLEX) |
| 274 | if (aimag(value%complex_val) >= 0.0_real64) then | 285 | if (aimag(value%complex_val) >= 0.0_real64) then |
| 275 | write(*, '(G0,"+",G0,"i")') real(value%complex_val), aimag(value%complex_val) | 286 | write(*, '(G0,"+",G0,"i")') real(value%complex_val), aimag(value%complex_val) |
test_results/arithmetic.logadded@@ -0,0 +1,26 @@ | |||
| 1 | +============================================================ | ||
| 2 | +Running Test Suite: Arithmetic Operations | ||
| 3 | +============================================================ | ||
| 4 | +✓ Addition: 2 + 3 = 5 ... PASSED | ||
| 5 | +✓ Addition: -5 + 3 = -2 ... PASSED | ||
| 6 | +✓ Addition: 0.1 + 0.2 ... PASSED | ||
| 7 | +✓ Subtraction: 10 - 3 = 7 ... PASSED | ||
| 8 | +✓ Subtraction: -5 - 3 = -8 ... PASSED | ||
| 9 | +✓ Multiplication: 4 * 5 = 20 ... PASSED | ||
| 10 | +✓ Multiplication: -3 * 7 = -21 ... PASSED | ||
| 11 | +✓ Multiplication: -2 * -3 = 6 ... PASSED | ||
| 12 | +✓ Division: 15 / 3 = 5 ... PASSED | ||
| 13 | +✓ Division: 7 / 2 = 3.5 ... PASSED | ||
| 14 | +✓ Division: -12 / 4 = -3 ... PASSED | ||
| 15 | +✓ Power: 2^3 = 8 ... PASSED | ||
| 16 | +✓ Power: 5^2 = 25 ... PASSED | ||
| 17 | +✓ Power: 4^0.5 = 2 ... PASSED | ||
| 18 | +✓ Power: 10^-1 = 0.1 ... PASSED | ||
| 19 | +✓ Negation: -(5) = -5 ... PASSED | ||
| 20 | +✓ Negation: -(-3) = 3 ... PASSED | ||
| 21 | +✓ Addition with zero: 5 + 0 = 5 ... PASSED | ||
| 22 | +✓ Multiplication by zero: 5 * 0 = 0 ... PASSED | ||
| 23 | +✓ Multiplication by one: 7 * 1 = 7 ... PASSED | ||
| 24 | +------------------------------------------------------------ | ||
| 25 | +Results: 20 passed, 0 failed, 20 total | ||
| 26 | +============================================================ | ||
tests/fixtures/basic_arithmetic.expectedadded@@ -0,0 +1,5 @@ | |||
| 1 | +5.0000000000000000 | ||
| 2 | +6.0000000000000000 | ||
| 3 | +30.000000000000000 | ||
| 4 | +5.0000000000000000 | ||
| 5 | +8.0000000000000000 | ||
tests/fixtures/basic_arithmetic.fbadded@@ -0,0 +1,6 @@ | |||
| 1 | +2 + 3 | ||
| 2 | +10 - 4 | ||
| 3 | +5 * 6 | ||
| 4 | +20 / 4 | ||
| 5 | +2 ^ 3 | ||
| 6 | +exit | ||
tests/integration/test_expressions.f90added@@ -0,0 +1,190 @@ | |||
| 1 | +!> Integration tests for complete expression evaluation | ||
| 2 | +program test_expressions | ||
| 3 | + use test_framework | ||
| 4 | + use fortbite_types_m, only: value_t, token_t, print_value | ||
| 5 | + use fortbite_lexer_m, only: tokenize, free_tokens | ||
| 6 | + use fortbite_parser_m, only: parse_expression, parse_error_t | ||
| 7 | + use fortbite_evaluator_m, only: evaluate_expression, evaluation_context_t, & | ||
| 8 | + evaluation_error_t, create_context | ||
| 9 | + use fortbite_ast_m, only: ast_node_t, free_ast | ||
| 10 | + use iso_fortran_env, only: real64 | ||
| 11 | + implicit none | ||
| 12 | + | ||
| 13 | + type(test_suite) :: suite | ||
| 14 | + type(evaluation_context_t) :: context | ||
| 15 | + type(value_t) :: result | ||
| 16 | + real(real64) :: expected | ||
| 17 | + | ||
| 18 | + ! Initialize test suite | ||
| 19 | + suite = init_test_suite('Expression Evaluation') | ||
| 20 | + context = create_context() | ||
| 21 | + | ||
| 22 | + ! Test simple arithmetic expressions | ||
| 23 | + call test_expression(suite, context, '2 + 3', 5.0_real64, 'Simple addition') | ||
| 24 | + call test_expression(suite, context, '10 - 4', 6.0_real64, 'Simple subtraction') | ||
| 25 | + call test_expression(suite, context, '3 * 7', 21.0_real64, 'Simple multiplication') | ||
| 26 | + call test_expression(suite, context, '15 / 3', 5.0_real64, 'Simple division') | ||
| 27 | + call test_expression(suite, context, '2 ^ 3', 8.0_real64, 'Power operation') | ||
| 28 | + call test_expression(suite, context, '2 ** 3', 8.0_real64, 'Power with ** operator') | ||
| 29 | + | ||
| 30 | + ! Test operator precedence | ||
| 31 | + call test_expression(suite, context, '2 + 3 * 4', 14.0_real64, 'Precedence: multiplication before addition') | ||
| 32 | + call test_expression(suite, context, '(2 + 3) * 4', 20.0_real64, 'Parentheses override precedence') | ||
| 33 | + call test_expression(suite, context, '10 - 2 * 3', 4.0_real64, 'Precedence: multiplication before subtraction') | ||
| 34 | + call test_expression(suite, context, '2 * 3 + 4 * 5', 26.0_real64, 'Multiple multiplications and addition') | ||
| 35 | + call test_expression(suite, context, '2 ^ 3 * 4', 32.0_real64, 'Power before multiplication') | ||
| 36 | + call test_expression(suite, context, '2 * 3 ^ 2', 18.0_real64, 'Power before multiplication (2)') | ||
| 37 | + | ||
| 38 | + ! Test nested expressions | ||
| 39 | + call test_expression(suite, context, '((2 + 3) * 4) / 2', 10.0_real64, 'Nested parentheses') | ||
| 40 | + call test_expression(suite, context, '2 * (3 + (4 * 5))', 46.0_real64, 'Multiple nesting levels') | ||
| 41 | + | ||
| 42 | + ! Test unary operators | ||
| 43 | + call test_expression(suite, context, '-5', -5.0_real64, 'Unary minus') | ||
| 44 | + call test_expression(suite, context, '+5', 5.0_real64, 'Unary plus') | ||
| 45 | + call test_expression(suite, context, '-(2 + 3)', -5.0_real64, 'Unary minus with expression') | ||
| 46 | + call test_expression(suite, context, '-2 * 3', -6.0_real64, 'Unary minus with multiplication') | ||
| 47 | + call test_expression(suite, context, '2 * -3', -6.0_real64, 'Multiplication with unary minus') | ||
| 48 | + | ||
| 49 | + ! Test floating point expressions | ||
| 50 | + call test_expression_near(suite, context, '0.1 + 0.2', 0.3_real64, 1.0e-10_real64, 'Floating point addition') | ||
| 51 | + call test_expression(suite, context, '3.5 * 2', 7.0_real64, 'Floating point multiplication') | ||
| 52 | + call test_expression(suite, context, '7.5 / 2.5', 3.0_real64, 'Floating point division') | ||
| 53 | + | ||
| 54 | + ! Test mathematical constants | ||
| 55 | + call test_expression_near(suite, context, 'pi', 3.141592653589793_real64, 1.0e-10_real64, 'Pi constant') | ||
| 56 | + call test_expression_near(suite, context, 'e', 2.718281828459045_real64, 1.0e-10_real64, 'E constant') | ||
| 57 | + call test_expression_near(suite, context, '2 * pi', 6.283185307179586_real64, 1.0e-10_real64, 'Expression with pi') | ||
| 58 | + | ||
| 59 | + ! Test precision specification (our recently fixed feature!) | ||
| 60 | + call test_precision(suite, context, 'pi::10', 10, 'Pi with 10 digit precision') | ||
| 61 | + call test_precision(suite, context, 'e::5', 5, 'E with 5 digit precision') | ||
| 62 | + | ||
| 63 | + ! Run the test suite | ||
| 64 | + call run_test_suite(suite) | ||
| 65 | + | ||
| 66 | +contains | ||
| 67 | + | ||
| 68 | + !> Test an expression evaluation | ||
| 69 | + subroutine test_expression(suite, context, expr_str, expected, test_name) | ||
| 70 | + type(test_suite), intent(inout) :: suite | ||
| 71 | + type(evaluation_context_t), intent(inout) :: context | ||
| 72 | + character(len=*), intent(in) :: expr_str | ||
| 73 | + real(real64), intent(in) :: expected | ||
| 74 | + character(len=*), intent(in) :: test_name | ||
| 75 | + | ||
| 76 | + type(token_t), allocatable :: tokens(:) | ||
| 77 | + type(ast_node_t), pointer :: ast | ||
| 78 | + type(parse_error_t) :: parse_err | ||
| 79 | + type(evaluation_error_t) :: eval_err | ||
| 80 | + type(value_t) :: result | ||
| 81 | + | ||
| 82 | + call add_test_case(suite, test_name) | ||
| 83 | + | ||
| 84 | + ! Tokenize | ||
| 85 | + tokens = tokenize(expr_str) | ||
| 86 | + | ||
| 87 | + ! Parse | ||
| 88 | + ast => parse_expression(tokens, parse_err) | ||
| 89 | + if (parse_err%has_error) then | ||
| 90 | + call assert_true(suite, .false., 'Parse error: ' // trim(parse_err%message)) | ||
| 91 | + call free_tokens(tokens) | ||
| 92 | + return | ||
| 93 | + end if | ||
| 94 | + | ||
| 95 | + ! Evaluate | ||
| 96 | + result = evaluate_expression(ast, context, eval_err) | ||
| 97 | + if (eval_err%has_error) then | ||
| 98 | + call assert_true(suite, .false., 'Evaluation error: ' // trim(eval_err%message)) | ||
| 99 | + else | ||
| 100 | + call assert_equals(suite, result%scalar_val, expected) | ||
| 101 | + end if | ||
| 102 | + | ||
| 103 | + ! Clean up | ||
| 104 | + call free_ast(ast) | ||
| 105 | + call free_tokens(tokens) | ||
| 106 | + end subroutine test_expression | ||
| 107 | + | ||
| 108 | + !> Test an expression evaluation with tolerance | ||
| 109 | + subroutine test_expression_near(suite, context, expr_str, expected, tolerance, test_name) | ||
| 110 | + type(test_suite), intent(inout) :: suite | ||
| 111 | + type(evaluation_context_t), intent(inout) :: context | ||
| 112 | + character(len=*), intent(in) :: expr_str | ||
| 113 | + real(real64), intent(in) :: expected, tolerance | ||
| 114 | + character(len=*), intent(in) :: test_name | ||
| 115 | + | ||
| 116 | + type(token_t), allocatable :: tokens(:) | ||
| 117 | + type(ast_node_t), pointer :: ast | ||
| 118 | + type(parse_error_t) :: parse_err | ||
| 119 | + type(evaluation_error_t) :: eval_err | ||
| 120 | + type(value_t) :: result | ||
| 121 | + | ||
| 122 | + call add_test_case(suite, test_name) | ||
| 123 | + | ||
| 124 | + ! Tokenize | ||
| 125 | + tokens = tokenize(expr_str) | ||
| 126 | + | ||
| 127 | + ! Parse | ||
| 128 | + ast => parse_expression(tokens, parse_err) | ||
| 129 | + if (parse_err%has_error) then | ||
| 130 | + call assert_true(suite, .false., 'Parse error: ' // trim(parse_err%message)) | ||
| 131 | + call free_tokens(tokens) | ||
| 132 | + return | ||
| 133 | + end if | ||
| 134 | + | ||
| 135 | + ! Evaluate | ||
| 136 | + result = evaluate_expression(ast, context, eval_err) | ||
| 137 | + if (eval_err%has_error) then | ||
| 138 | + call assert_true(suite, .false., 'Evaluation error: ' // trim(eval_err%message)) | ||
| 139 | + else | ||
| 140 | + call assert_near(suite, result%scalar_val, expected, tolerance) | ||
| 141 | + end if | ||
| 142 | + | ||
| 143 | + ! Clean up | ||
| 144 | + call free_ast(ast) | ||
| 145 | + call free_tokens(tokens) | ||
| 146 | + end subroutine test_expression_near | ||
| 147 | + | ||
| 148 | + !> Test precision specification | ||
| 149 | + subroutine test_precision(suite, context, expr_str, expected_precision, test_name) | ||
| 150 | + type(test_suite), intent(inout) :: suite | ||
| 151 | + type(evaluation_context_t), intent(inout) :: context | ||
| 152 | + character(len=*), intent(in) :: expr_str | ||
| 153 | + integer, intent(in) :: expected_precision | ||
| 154 | + character(len=*), intent(in) :: test_name | ||
| 155 | + | ||
| 156 | + type(token_t), allocatable :: tokens(:) | ||
| 157 | + type(ast_node_t), pointer :: ast | ||
| 158 | + type(parse_error_t) :: parse_err | ||
| 159 | + type(evaluation_error_t) :: eval_err | ||
| 160 | + type(value_t) :: result | ||
| 161 | + | ||
| 162 | + call add_test_case(suite, test_name) | ||
| 163 | + | ||
| 164 | + ! Tokenize | ||
| 165 | + tokens = tokenize(expr_str) | ||
| 166 | + | ||
| 167 | + ! Parse | ||
| 168 | + ast => parse_expression(tokens, parse_err) | ||
| 169 | + if (parse_err%has_error) then | ||
| 170 | + call assert_true(suite, .false., 'Parse error: ' // trim(parse_err%message)) | ||
| 171 | + call free_tokens(tokens) | ||
| 172 | + return | ||
| 173 | + end if | ||
| 174 | + | ||
| 175 | + ! Evaluate | ||
| 176 | + result = evaluate_expression(ast, context, eval_err) | ||
| 177 | + if (eval_err%has_error) then | ||
| 178 | + call assert_true(suite, .false., 'Evaluation error: ' // trim(eval_err%message)) | ||
| 179 | + else | ||
| 180 | + ! Check that precision_kind was set correctly | ||
| 181 | + call assert_true(suite, result%precision_kind == expected_precision, & | ||
| 182 | + 'Precision specification stored correctly') | ||
| 183 | + end if | ||
| 184 | + | ||
| 185 | + ! Clean up | ||
| 186 | + call free_ast(ast) | ||
| 187 | + call free_tokens(tokens) | ||
| 188 | + end subroutine test_precision | ||
| 189 | + | ||
| 190 | +end program test_expressions | ||
tests/run_tests.shadded@@ -0,0 +1,131 @@ | |||
| 1 | +#!/bin/bash | ||
| 2 | + | ||
| 3 | +# FORTBITE Test Runner Script | ||
| 4 | +# Runs all unit and integration tests | ||
| 5 | + | ||
| 6 | +set -e # Exit on error | ||
| 7 | + | ||
| 8 | +# Colors for output | ||
| 9 | +RED='\033[0;31m' | ||
| 10 | +GREEN='\033[0;32m' | ||
| 11 | +YELLOW='\033[1;33m' | ||
| 12 | +NC='\033[0m' # No Color | ||
| 13 | + | ||
| 14 | +echo "================================" | ||
| 15 | +echo " FORTBITE Test Suite" | ||
| 16 | +echo "================================" | ||
| 17 | +echo | ||
| 18 | + | ||
| 19 | +# Create test results directory | ||
| 20 | +mkdir -p test_results | ||
| 21 | + | ||
| 22 | +# Track overall results | ||
| 23 | +TOTAL_TESTS=0 | ||
| 24 | +PASSED_TESTS=0 | ||
| 25 | +FAILED_TESTS=0 | ||
| 26 | +FAILED_LIST="" | ||
| 27 | + | ||
| 28 | +# Function to run a test | ||
| 29 | +run_test() { | ||
| 30 | + local test_name=$1 | ||
| 31 | + local test_exec=$2 | ||
| 32 | + local test_type=$3 | ||
| 33 | + | ||
| 34 | + echo -n "Running $test_type test: $test_name ... " | ||
| 35 | + | ||
| 36 | + if [ -f "$test_exec" ]; then | ||
| 37 | + if $test_exec > "test_results/${test_name}.log" 2>&1; then | ||
| 38 | + echo -e "${GREEN}PASSED${NC}" | ||
| 39 | + ((PASSED_TESTS++)) | ||
| 40 | + else | ||
| 41 | + echo -e "${RED}FAILED${NC}" | ||
| 42 | + ((FAILED_TESTS++)) | ||
| 43 | + FAILED_LIST="$FAILED_LIST\n - $test_name" | ||
| 44 | + echo " Error output:" | ||
| 45 | + tail -n 5 "test_results/${test_name}.log" | sed 's/^/ /' | ||
| 46 | + fi | ||
| 47 | + else | ||
| 48 | + echo -e "${YELLOW}NOT FOUND${NC} (skipping)" | ||
| 49 | + fi | ||
| 50 | + ((TOTAL_TESTS++)) | ||
| 51 | +} | ||
| 52 | + | ||
| 53 | +# Build tests if needed | ||
| 54 | +if [ "$1" != "--no-build" ]; then | ||
| 55 | + echo "Building tests..." | ||
| 56 | + make test-build || exit 1 | ||
| 57 | + echo | ||
| 58 | +fi | ||
| 59 | + | ||
| 60 | +# Run unit tests | ||
| 61 | +echo "=== Unit Tests ===" | ||
| 62 | +run_test "arithmetic" "build/tests/unit/test_arithmetic" "unit" | ||
| 63 | +run_test "functions" "build/tests/unit/test_functions" "unit" | ||
| 64 | +run_test "complex" "build/tests/unit/test_complex" "unit" | ||
| 65 | +run_test "matrix" "build/tests/unit/test_matrix" "unit" | ||
| 66 | +echo | ||
| 67 | + | ||
| 68 | +# Run integration tests | ||
| 69 | +echo "=== Integration Tests ===" | ||
| 70 | +run_test "expressions" "build/tests/integration/test_expressions" "integration" | ||
| 71 | +run_test "variables" "build/tests/integration/test_variables" "integration" | ||
| 72 | +echo | ||
| 73 | + | ||
| 74 | +# Run end-to-end tests using test scripts | ||
| 75 | +echo "=== End-to-End Tests ===" | ||
| 76 | +if [ -d "tests/fixtures" ]; then | ||
| 77 | + for fixture in tests/fixtures/*.fb; do | ||
| 78 | + if [ -f "$fixture" ]; then | ||
| 79 | + test_name=$(basename "$fixture" .fb) | ||
| 80 | + echo -n "Running E2E test: $test_name ... " | ||
| 81 | + | ||
| 82 | + expected_output="${fixture%.fb}.expected" | ||
| 83 | + if [ -f "$expected_output" ]; then | ||
| 84 | + if ./build/bin/fortbite < "$fixture" > "test_results/${test_name}.out" 2>&1; then | ||
| 85 | + # Strip prompts and compare output | ||
| 86 | + grep -v "fortbite>" "test_results/${test_name}.out" | \ | ||
| 87 | + grep -v "======" | \ | ||
| 88 | + grep -v "Type" | \ | ||
| 89 | + grep -v "Use" | \ | ||
| 90 | + grep -v "Goodbye" > "test_results/${test_name}.clean" | ||
| 91 | + | ||
| 92 | + if diff -q "$expected_output" "test_results/${test_name}.clean" > /dev/null; then | ||
| 93 | + echo -e "${GREEN}PASSED${NC}" | ||
| 94 | + ((PASSED_TESTS++)) | ||
| 95 | + else | ||
| 96 | + echo -e "${RED}FAILED${NC} (output mismatch)" | ||
| 97 | + ((FAILED_TESTS++)) | ||
| 98 | + FAILED_LIST="$FAILED_LIST\n - E2E: $test_name" | ||
| 99 | + fi | ||
| 100 | + else | ||
| 101 | + echo -e "${RED}FAILED${NC} (execution error)" | ||
| 102 | + ((FAILED_TESTS++)) | ||
| 103 | + FAILED_LIST="$FAILED_LIST\n - E2E: $test_name" | ||
| 104 | + fi | ||
| 105 | + else | ||
| 106 | + echo -e "${YELLOW}NO EXPECTED OUTPUT${NC} (skipping)" | ||
| 107 | + fi | ||
| 108 | + ((TOTAL_TESTS++)) | ||
| 109 | + fi | ||
| 110 | + done | ||
| 111 | +fi | ||
| 112 | +echo | ||
| 113 | + | ||
| 114 | +# Summary | ||
| 115 | +echo "================================" | ||
| 116 | +echo " Test Summary" | ||
| 117 | +echo "================================" | ||
| 118 | +echo -e "Total: $TOTAL_TESTS tests" | ||
| 119 | +echo -e "Passed: ${GREEN}$PASSED_TESTS${NC} tests" | ||
| 120 | +echo -e "Failed: ${RED}$FAILED_TESTS${NC} tests" | ||
| 121 | + | ||
| 122 | +if [ $FAILED_TESTS -gt 0 ]; then | ||
| 123 | + echo | ||
| 124 | + echo -e "${RED}Failed tests:${NC}" | ||
| 125 | + echo -e "$FAILED_LIST" | ||
| 126 | + exit 1 | ||
| 127 | +else | ||
| 128 | + echo | ||
| 129 | + echo -e "${GREEN}All tests passed!${NC}" | ||
| 130 | + exit 0 | ||
| 131 | +fi | ||
tests/test_framework.f90added@@ -0,0 +1,179 @@ | |||
| 1 | +!> Test framework module for FORTBITE | ||
| 2 | +!> Provides utilities for unit testing | ||
| 3 | +module test_framework | ||
| 4 | + use iso_fortran_env, only: real64, output_unit, error_unit | ||
| 5 | + implicit none | ||
| 6 | + private | ||
| 7 | + | ||
| 8 | + public :: test_suite, test_case, assert_equals, assert_true, assert_false | ||
| 9 | + public :: assert_near, run_test_suite, init_test_suite, add_test_case | ||
| 10 | + | ||
| 11 | + !> Test case type | ||
| 12 | + type :: test_case | ||
| 13 | + character(len=100) :: name = '' | ||
| 14 | + logical :: passed = .true. | ||
| 15 | + character(len=200) :: failure_message = '' | ||
| 16 | + end type test_case | ||
| 17 | + | ||
| 18 | + !> Test suite type | ||
| 19 | + type :: test_suite | ||
| 20 | + character(len=100) :: name = '' | ||
| 21 | + type(test_case) :: tests(100) | ||
| 22 | + integer :: test_count = 0 | ||
| 23 | + integer :: passed_count = 0 | ||
| 24 | + integer :: failed_count = 0 | ||
| 25 | + integer :: current_test_index = 0 | ||
| 26 | + end type test_suite | ||
| 27 | + | ||
| 28 | +contains | ||
| 29 | + | ||
| 30 | + !> Initialize a test suite | ||
| 31 | + function init_test_suite(name) result(suite) | ||
| 32 | + character(len=*), intent(in) :: name | ||
| 33 | + type(test_suite) :: suite | ||
| 34 | + | ||
| 35 | + suite%name = trim(name) | ||
| 36 | + suite%test_count = 0 | ||
| 37 | + suite%passed_count = 0 | ||
| 38 | + suite%failed_count = 0 | ||
| 39 | + suite%current_test_index = 0 | ||
| 40 | + end function init_test_suite | ||
| 41 | + | ||
| 42 | + !> Add a test case to the suite | ||
| 43 | + subroutine add_test_case(suite, name) | ||
| 44 | + type(test_suite), intent(inout) :: suite | ||
| 45 | + character(len=*), intent(in) :: name | ||
| 46 | + | ||
| 47 | + suite%test_count = suite%test_count + 1 | ||
| 48 | + if (suite%test_count > size(suite%tests)) then | ||
| 49 | + write(error_unit, *) 'ERROR: Too many test cases!' | ||
| 50 | + return | ||
| 51 | + end if | ||
| 52 | + | ||
| 53 | + suite%current_test_index = suite%test_count | ||
| 54 | + suite%tests(suite%test_count)%name = trim(name) | ||
| 55 | + suite%tests(suite%test_count)%passed = .true. | ||
| 56 | + suite%tests(suite%test_count)%failure_message = '' | ||
| 57 | + end subroutine add_test_case | ||
| 58 | + | ||
| 59 | + !> Run all tests in a suite | ||
| 60 | + subroutine run_test_suite(suite) | ||
| 61 | + type(test_suite), intent(inout) :: suite | ||
| 62 | + integer :: i | ||
| 63 | + | ||
| 64 | + write(output_unit, '(A)') repeat('=', 60) | ||
| 65 | + write(output_unit, '(A,A)') 'Running Test Suite: ', trim(suite%name) | ||
| 66 | + write(output_unit, '(A)') repeat('=', 60) | ||
| 67 | + | ||
| 68 | + suite%passed_count = 0 | ||
| 69 | + suite%failed_count = 0 | ||
| 70 | + | ||
| 71 | + do i = 1, suite%test_count | ||
| 72 | + if (suite%tests(i)%passed) then | ||
| 73 | + suite%passed_count = suite%passed_count + 1 | ||
| 74 | + write(output_unit, '(A,A,A)') '✓ ', trim(suite%tests(i)%name), ' ... PASSED' | ||
| 75 | + else | ||
| 76 | + suite%failed_count = suite%failed_count + 1 | ||
| 77 | + write(output_unit, '(A,A,A)') '✗ ', trim(suite%tests(i)%name), ' ... FAILED' | ||
| 78 | + if (len_trim(suite%tests(i)%failure_message) > 0) then | ||
| 79 | + write(output_unit, '(A,A)') ' ', trim(suite%tests(i)%failure_message) | ||
| 80 | + end if | ||
| 81 | + end if | ||
| 82 | + end do | ||
| 83 | + | ||
| 84 | + write(output_unit, '(A)') repeat('-', 60) | ||
| 85 | + write(output_unit, '(A,I0,A,I0,A,I0,A)') 'Results: ', & | ||
| 86 | + suite%passed_count, ' passed, ', & | ||
| 87 | + suite%failed_count, ' failed, ', & | ||
| 88 | + suite%test_count, ' total' | ||
| 89 | + write(output_unit, '(A)') repeat('=', 60) | ||
| 90 | + end subroutine run_test_suite | ||
| 91 | + | ||
| 92 | + !> Assert that two real values are equal | ||
| 93 | + subroutine assert_equals(suite, actual, expected, message) | ||
| 94 | + type(test_suite), intent(inout) :: suite | ||
| 95 | + real(real64), intent(in) :: actual, expected | ||
| 96 | + character(len=*), intent(in), optional :: message | ||
| 97 | + | ||
| 98 | + character(len=200) :: fail_msg | ||
| 99 | + integer :: idx | ||
| 100 | + | ||
| 101 | + idx = suite%current_test_index | ||
| 102 | + if (idx < 1 .or. idx > suite%test_count) return | ||
| 103 | + | ||
| 104 | + if (abs(actual - expected) > epsilon(1.0_real64)) then | ||
| 105 | + write(fail_msg, '(A,G0,A,G0)') 'Expected: ', expected, ', Got: ', actual | ||
| 106 | + if (present(message)) then | ||
| 107 | + fail_msg = trim(message) // ' - ' // trim(fail_msg) | ||
| 108 | + end if | ||
| 109 | + suite%tests(idx)%passed = .false. | ||
| 110 | + suite%tests(idx)%failure_message = trim(fail_msg) | ||
| 111 | + end if | ||
| 112 | + end subroutine assert_equals | ||
| 113 | + | ||
| 114 | + !> Assert that two real values are approximately equal | ||
| 115 | + subroutine assert_near(suite, actual, expected, tolerance, message) | ||
| 116 | + type(test_suite), intent(inout) :: suite | ||
| 117 | + real(real64), intent(in) :: actual, expected, tolerance | ||
| 118 | + character(len=*), intent(in), optional :: message | ||
| 119 | + | ||
| 120 | + character(len=200) :: fail_msg | ||
| 121 | + integer :: idx | ||
| 122 | + | ||
| 123 | + idx = suite%current_test_index | ||
| 124 | + if (idx < 1 .or. idx > suite%test_count) return | ||
| 125 | + | ||
| 126 | + if (abs(actual - expected) > tolerance) then | ||
| 127 | + write(fail_msg, '(A,G0,A,G0,A,G0)') 'Expected: ', expected, & | ||
| 128 | + ' (±', tolerance, '), Got: ', actual | ||
| 129 | + if (present(message)) then | ||
| 130 | + fail_msg = trim(message) // ' - ' // trim(fail_msg) | ||
| 131 | + end if | ||
| 132 | + suite%tests(idx)%passed = .false. | ||
| 133 | + suite%tests(idx)%failure_message = trim(fail_msg) | ||
| 134 | + end if | ||
| 135 | + end subroutine assert_near | ||
| 136 | + | ||
| 137 | + !> Assert that a condition is true | ||
| 138 | + subroutine assert_true(suite, condition, message) | ||
| 139 | + type(test_suite), intent(inout) :: suite | ||
| 140 | + logical, intent(in) :: condition | ||
| 141 | + character(len=*), intent(in), optional :: message | ||
| 142 | + | ||
| 143 | + integer :: idx | ||
| 144 | + | ||
| 145 | + idx = suite%current_test_index | ||
| 146 | + if (idx < 1 .or. idx > suite%test_count) return | ||
| 147 | + | ||
| 148 | + if (.not. condition) then | ||
| 149 | + suite%tests(idx)%passed = .false. | ||
| 150 | + if (present(message)) then | ||
| 151 | + suite%tests(idx)%failure_message = trim(message) | ||
| 152 | + else | ||
| 153 | + suite%tests(idx)%failure_message = 'Assertion failed: Expected true, got false' | ||
| 154 | + end if | ||
| 155 | + end if | ||
| 156 | + end subroutine assert_true | ||
| 157 | + | ||
| 158 | + !> Assert that a condition is false | ||
| 159 | + subroutine assert_false(suite, condition, message) | ||
| 160 | + type(test_suite), intent(inout) :: suite | ||
| 161 | + logical, intent(in) :: condition | ||
| 162 | + character(len=*), intent(in), optional :: message | ||
| 163 | + | ||
| 164 | + integer :: idx | ||
| 165 | + | ||
| 166 | + idx = suite%current_test_index | ||
| 167 | + if (idx < 1 .or. idx > suite%test_count) return | ||
| 168 | + | ||
| 169 | + if (condition) then | ||
| 170 | + suite%tests(idx)%passed = .false. | ||
| 171 | + if (present(message)) then | ||
| 172 | + suite%tests(idx)%failure_message = trim(message) | ||
| 173 | + else | ||
| 174 | + suite%tests(idx)%failure_message = 'Assertion failed: Expected false, got true' | ||
| 175 | + end if | ||
| 176 | + end if | ||
| 177 | + end subroutine assert_false | ||
| 178 | + | ||
| 179 | +end module test_framework | ||
tests/unit/test_arithmetic.f90added@@ -0,0 +1,145 @@ | |||
| 1 | +!> Unit tests for arithmetic operations | ||
| 2 | +program test_arithmetic | ||
| 3 | + use test_framework | ||
| 4 | + use fortbite_types_m, only: value_t, create_scalar, VALUE_SCALAR | ||
| 5 | + use fortbite_arithmetic_m, only: add_values, subtract_values, multiply_values, & | ||
| 6 | + divide_values, power_values, negate_value | ||
| 7 | + use iso_fortran_env, only: real64 | ||
| 8 | + implicit none | ||
| 9 | + | ||
| 10 | + type(test_suite) :: suite | ||
| 11 | + type(value_t) :: a, b, result | ||
| 12 | + real(real64) :: expected | ||
| 13 | + | ||
| 14 | + ! Initialize test suite | ||
| 15 | + suite = init_test_suite('Arithmetic Operations') | ||
| 16 | + | ||
| 17 | + ! Test addition | ||
| 18 | + call add_test_case(suite, 'Addition: 2 + 3 = 5') | ||
| 19 | + a = create_scalar(2.0_real64) | ||
| 20 | + b = create_scalar(3.0_real64) | ||
| 21 | + result = add_values(a, b) | ||
| 22 | + call assert_equals(suite, result%scalar_val, 5.0_real64) | ||
| 23 | + | ||
| 24 | + call add_test_case(suite, 'Addition: -5 + 3 = -2') | ||
| 25 | + a = create_scalar(-5.0_real64) | ||
| 26 | + b = create_scalar(3.0_real64) | ||
| 27 | + result = add_values(a, b) | ||
| 28 | + call assert_equals(suite, result%scalar_val, -2.0_real64) | ||
| 29 | + | ||
| 30 | + call add_test_case(suite, 'Addition: 0.1 + 0.2') | ||
| 31 | + a = create_scalar(0.1_real64) | ||
| 32 | + b = create_scalar(0.2_real64) | ||
| 33 | + result = add_values(a, b) | ||
| 34 | + call assert_near(suite, result%scalar_val, 0.3_real64, 1.0e-10_real64) | ||
| 35 | + | ||
| 36 | + ! Test subtraction | ||
| 37 | + call add_test_case(suite, 'Subtraction: 10 - 3 = 7') | ||
| 38 | + a = create_scalar(10.0_real64) | ||
| 39 | + b = create_scalar(3.0_real64) | ||
| 40 | + result = subtract_values(a, b) | ||
| 41 | + call assert_equals(suite, result%scalar_val, 7.0_real64) | ||
| 42 | + | ||
| 43 | + call add_test_case(suite, 'Subtraction: -5 - 3 = -8') | ||
| 44 | + a = create_scalar(-5.0_real64) | ||
| 45 | + b = create_scalar(3.0_real64) | ||
| 46 | + result = subtract_values(a, b) | ||
| 47 | + call assert_equals(suite, result%scalar_val, -8.0_real64) | ||
| 48 | + | ||
| 49 | + ! Test multiplication | ||
| 50 | + call add_test_case(suite, 'Multiplication: 4 * 5 = 20') | ||
| 51 | + a = create_scalar(4.0_real64) | ||
| 52 | + b = create_scalar(5.0_real64) | ||
| 53 | + result = multiply_values(a, b) | ||
| 54 | + call assert_equals(suite, result%scalar_val, 20.0_real64) | ||
| 55 | + | ||
| 56 | + call add_test_case(suite, 'Multiplication: -3 * 7 = -21') | ||
| 57 | + a = create_scalar(-3.0_real64) | ||
| 58 | + b = create_scalar(7.0_real64) | ||
| 59 | + result = multiply_values(a, b) | ||
| 60 | + call assert_equals(suite, result%scalar_val, -21.0_real64) | ||
| 61 | + | ||
| 62 | + call add_test_case(suite, 'Multiplication: -2 * -3 = 6') | ||
| 63 | + a = create_scalar(-2.0_real64) | ||
| 64 | + b = create_scalar(-3.0_real64) | ||
| 65 | + result = multiply_values(a, b) | ||
| 66 | + call assert_equals(suite, result%scalar_val, 6.0_real64) | ||
| 67 | + | ||
| 68 | + ! Test division | ||
| 69 | + call add_test_case(suite, 'Division: 15 / 3 = 5') | ||
| 70 | + a = create_scalar(15.0_real64) | ||
| 71 | + b = create_scalar(3.0_real64) | ||
| 72 | + result = divide_values(a, b) | ||
| 73 | + call assert_equals(suite, result%scalar_val, 5.0_real64) | ||
| 74 | + | ||
| 75 | + call add_test_case(suite, 'Division: 7 / 2 = 3.5') | ||
| 76 | + a = create_scalar(7.0_real64) | ||
| 77 | + b = create_scalar(2.0_real64) | ||
| 78 | + result = divide_values(a, b) | ||
| 79 | + call assert_equals(suite, result%scalar_val, 3.5_real64) | ||
| 80 | + | ||
| 81 | + call add_test_case(suite, 'Division: -12 / 4 = -3') | ||
| 82 | + a = create_scalar(-12.0_real64) | ||
| 83 | + b = create_scalar(4.0_real64) | ||
| 84 | + result = divide_values(a, b) | ||
| 85 | + call assert_equals(suite, result%scalar_val, -3.0_real64) | ||
| 86 | + | ||
| 87 | + ! Test power operations | ||
| 88 | + call add_test_case(suite, 'Power: 2^3 = 8') | ||
| 89 | + a = create_scalar(2.0_real64) | ||
| 90 | + b = create_scalar(3.0_real64) | ||
| 91 | + result = power_values(a, b) | ||
| 92 | + call assert_equals(suite, result%scalar_val, 8.0_real64) | ||
| 93 | + | ||
| 94 | + call add_test_case(suite, 'Power: 5^2 = 25') | ||
| 95 | + a = create_scalar(5.0_real64) | ||
| 96 | + b = create_scalar(2.0_real64) | ||
| 97 | + result = power_values(a, b) | ||
| 98 | + call assert_equals(suite, result%scalar_val, 25.0_real64) | ||
| 99 | + | ||
| 100 | + call add_test_case(suite, 'Power: 4^0.5 = 2') | ||
| 101 | + a = create_scalar(4.0_real64) | ||
| 102 | + b = create_scalar(0.5_real64) | ||
| 103 | + result = power_values(a, b) | ||
| 104 | + call assert_equals(suite, result%scalar_val, 2.0_real64) | ||
| 105 | + | ||
| 106 | + call add_test_case(suite, 'Power: 10^-1 = 0.1') | ||
| 107 | + a = create_scalar(10.0_real64) | ||
| 108 | + b = create_scalar(-1.0_real64) | ||
| 109 | + result = power_values(a, b) | ||
| 110 | + call assert_near(suite, result%scalar_val, 0.1_real64, 1.0e-10_real64) | ||
| 111 | + | ||
| 112 | + ! Test negation | ||
| 113 | + call add_test_case(suite, 'Negation: -(5) = -5') | ||
| 114 | + a = create_scalar(5.0_real64) | ||
| 115 | + result = negate_value(a) | ||
| 116 | + call assert_equals(suite, result%scalar_val, -5.0_real64) | ||
| 117 | + | ||
| 118 | + call add_test_case(suite, 'Negation: -(-3) = 3') | ||
| 119 | + a = create_scalar(-3.0_real64) | ||
| 120 | + result = negate_value(a) | ||
| 121 | + call assert_equals(suite, result%scalar_val, 3.0_real64) | ||
| 122 | + | ||
| 123 | + ! Test special cases | ||
| 124 | + call add_test_case(suite, 'Addition with zero: 5 + 0 = 5') | ||
| 125 | + a = create_scalar(5.0_real64) | ||
| 126 | + b = create_scalar(0.0_real64) | ||
| 127 | + result = add_values(a, b) | ||
| 128 | + call assert_equals(suite, result%scalar_val, 5.0_real64) | ||
| 129 | + | ||
| 130 | + call add_test_case(suite, 'Multiplication by zero: 5 * 0 = 0') | ||
| 131 | + a = create_scalar(5.0_real64) | ||
| 132 | + b = create_scalar(0.0_real64) | ||
| 133 | + result = multiply_values(a, b) | ||
| 134 | + call assert_equals(suite, result%scalar_val, 0.0_real64) | ||
| 135 | + | ||
| 136 | + call add_test_case(suite, 'Multiplication by one: 7 * 1 = 7') | ||
| 137 | + a = create_scalar(7.0_real64) | ||
| 138 | + b = create_scalar(1.0_real64) | ||
| 139 | + result = multiply_values(a, b) | ||
| 140 | + call assert_equals(suite, result%scalar_val, 7.0_real64) | ||
| 141 | + | ||
| 142 | + ! Run the test suite | ||
| 143 | + call run_test_suite(suite) | ||
| 144 | + | ||
| 145 | +end program test_arithmetic | ||
tests/unit/test_functions.f90added@@ -0,0 +1,199 @@ | |||
| 1 | +!> Unit tests for mathematical functions | ||
| 2 | +program test_functions | ||
| 3 | + use test_framework | ||
| 4 | + use fortbite_types_m, only: value_t, create_scalar, create_complex, VALUE_SCALAR, VALUE_COMPLEX | ||
| 5 | + use fortbite_functions_m, only: eval_trigonometric, eval_hyperbolic, eval_logarithmic, & | ||
| 6 | + eval_exponential, eval_statistical, eval_special, & | ||
| 7 | + eval_complex_functions | ||
| 8 | + use iso_fortran_env, only: real64 | ||
| 9 | + implicit none | ||
| 10 | + | ||
| 11 | + type(test_suite) :: suite | ||
| 12 | + type(value_t) :: arg, result | ||
| 13 | + real(real64) :: pi | ||
| 14 | + | ||
| 15 | + ! Initialize test suite | ||
| 16 | + suite = init_test_suite('Mathematical Functions') | ||
| 17 | + pi = 4.0_real64 * atan(1.0_real64) | ||
| 18 | + | ||
| 19 | + ! Test trigonometric functions | ||
| 20 | + call add_test_case(suite, 'sin(0) = 0') | ||
| 21 | + arg = create_scalar(0.0_real64) | ||
| 22 | + result = eval_trigonometric('sin', arg) | ||
| 23 | + call assert_near(suite, result%scalar_val, 0.0_real64, 1.0e-10_real64) | ||
| 24 | + | ||
| 25 | + call add_test_case(suite, 'sin(pi/2) = 1') | ||
| 26 | + arg = create_scalar(pi/2.0_real64) | ||
| 27 | + result = eval_trigonometric('sin', arg) | ||
| 28 | + call assert_near(suite, result%scalar_val, 1.0_real64, 1.0e-10_real64) | ||
| 29 | + | ||
| 30 | + call add_test_case(suite, 'cos(0) = 1') | ||
| 31 | + arg = create_scalar(0.0_real64) | ||
| 32 | + result = eval_trigonometric('cos', arg) | ||
| 33 | + call assert_near(suite, result%scalar_val, 1.0_real64, 1.0e-10_real64) | ||
| 34 | + | ||
| 35 | + call add_test_case(suite, 'cos(pi) = -1') | ||
| 36 | + arg = create_scalar(pi) | ||
| 37 | + result = eval_trigonometric('cos', arg) | ||
| 38 | + call assert_near(suite, result%scalar_val, -1.0_real64, 1.0e-10_real64) | ||
| 39 | + | ||
| 40 | + call add_test_case(suite, 'tan(0) = 0') | ||
| 41 | + arg = create_scalar(0.0_real64) | ||
| 42 | + result = eval_trigonometric('tan', arg) | ||
| 43 | + call assert_near(suite, result%scalar_val, 0.0_real64, 1.0e-10_real64) | ||
| 44 | + | ||
| 45 | + call add_test_case(suite, 'tan(pi/4) = 1') | ||
| 46 | + arg = create_scalar(pi/4.0_real64) | ||
| 47 | + result = eval_trigonometric('tan', arg) | ||
| 48 | + call assert_near(suite, result%scalar_val, 1.0_real64, 1.0e-10_real64) | ||
| 49 | + | ||
| 50 | + ! Test inverse trigonometric functions | ||
| 51 | + call add_test_case(suite, 'asin(0) = 0') | ||
| 52 | + arg = create_scalar(0.0_real64) | ||
| 53 | + result = eval_trigonometric('asin', arg) | ||
| 54 | + call assert_near(suite, result%scalar_val, 0.0_real64, 1.0e-10_real64) | ||
| 55 | + | ||
| 56 | + call add_test_case(suite, 'asin(1) = pi/2') | ||
| 57 | + arg = create_scalar(1.0_real64) | ||
| 58 | + result = eval_trigonometric('asin', arg) | ||
| 59 | + call assert_near(suite, result%scalar_val, pi/2.0_real64, 1.0e-10_real64) | ||
| 60 | + | ||
| 61 | + call add_test_case(suite, 'acos(1) = 0') | ||
| 62 | + arg = create_scalar(1.0_real64) | ||
| 63 | + result = eval_trigonometric('acos', arg) | ||
| 64 | + call assert_near(suite, result%scalar_val, 0.0_real64, 1.0e-10_real64) | ||
| 65 | + | ||
| 66 | + call add_test_case(suite, 'atan(1) = pi/4') | ||
| 67 | + arg = create_scalar(1.0_real64) | ||
| 68 | + result = eval_trigonometric('atan', arg) | ||
| 69 | + call assert_near(suite, result%scalar_val, pi/4.0_real64, 1.0e-10_real64) | ||
| 70 | + | ||
| 71 | + ! Test hyperbolic functions | ||
| 72 | + call add_test_case(suite, 'sinh(0) = 0') | ||
| 73 | + arg = create_scalar(0.0_real64) | ||
| 74 | + result = eval_hyperbolic('sinh', arg) | ||
| 75 | + call assert_near(suite, result%scalar_val, 0.0_real64, 1.0e-10_real64) | ||
| 76 | + | ||
| 77 | + call add_test_case(suite, 'cosh(0) = 1') | ||
| 78 | + arg = create_scalar(0.0_real64) | ||
| 79 | + result = eval_hyperbolic('cosh', arg) | ||
| 80 | + call assert_near(suite, result%scalar_val, 1.0_real64, 1.0e-10_real64) | ||
| 81 | + | ||
| 82 | + call add_test_case(suite, 'tanh(0) = 0') | ||
| 83 | + arg = create_scalar(0.0_real64) | ||
| 84 | + result = eval_hyperbolic('tanh', arg) | ||
| 85 | + call assert_near(suite, result%scalar_val, 0.0_real64, 1.0e-10_real64) | ||
| 86 | + | ||
| 87 | + ! Test logarithmic functions | ||
| 88 | + call add_test_case(suite, 'log(e) = 1') | ||
| 89 | + arg = create_scalar(exp(1.0_real64)) | ||
| 90 | + result = eval_logarithmic('log', arg) | ||
| 91 | + call assert_near(suite, result%scalar_val, 1.0_real64, 1.0e-10_real64) | ||
| 92 | + | ||
| 93 | + call add_test_case(suite, 'ln(e) = 1') | ||
| 94 | + arg = create_scalar(exp(1.0_real64)) | ||
| 95 | + result = eval_logarithmic('ln', arg) | ||
| 96 | + call assert_near(suite, result%scalar_val, 1.0_real64, 1.0e-10_real64) | ||
| 97 | + | ||
| 98 | + call add_test_case(suite, 'log10(10) = 1') | ||
| 99 | + arg = create_scalar(10.0_real64) | ||
| 100 | + result = eval_logarithmic('log10', arg) | ||
| 101 | + call assert_near(suite, result%scalar_val, 1.0_real64, 1.0e-10_real64) | ||
| 102 | + | ||
| 103 | + call add_test_case(suite, 'log10(100) = 2') | ||
| 104 | + arg = create_scalar(100.0_real64) | ||
| 105 | + result = eval_logarithmic('log10', arg) | ||
| 106 | + call assert_near(suite, result%scalar_val, 2.0_real64, 1.0e-10_real64) | ||
| 107 | + | ||
| 108 | + call add_test_case(suite, 'log2(8) = 3') | ||
| 109 | + arg = create_scalar(8.0_real64) | ||
| 110 | + result = eval_logarithmic('log2', arg) | ||
| 111 | + call assert_near(suite, result%scalar_val, 3.0_real64, 1.0e-10_real64) | ||
| 112 | + | ||
| 113 | + ! Test exponential functions | ||
| 114 | + call add_test_case(suite, 'exp(0) = 1') | ||
| 115 | + arg = create_scalar(0.0_real64) | ||
| 116 | + result = eval_exponential('exp', arg) | ||
| 117 | + call assert_near(suite, result%scalar_val, 1.0_real64, 1.0e-10_real64) | ||
| 118 | + | ||
| 119 | + call add_test_case(suite, 'exp(1) = e') | ||
| 120 | + arg = create_scalar(1.0_real64) | ||
| 121 | + result = eval_exponential('exp', arg) | ||
| 122 | + call assert_near(suite, result%scalar_val, exp(1.0_real64), 1.0e-10_real64) | ||
| 123 | + | ||
| 124 | + call add_test_case(suite, 'exp2(3) = 8') | ||
| 125 | + arg = create_scalar(3.0_real64) | ||
| 126 | + result = eval_exponential('exp2', arg) | ||
| 127 | + call assert_near(suite, result%scalar_val, 8.0_real64, 1.0e-10_real64) | ||
| 128 | + | ||
| 129 | + call add_test_case(suite, 'exp10(2) = 100') | ||
| 130 | + arg = create_scalar(2.0_real64) | ||
| 131 | + result = eval_exponential('exp10', arg) | ||
| 132 | + call assert_near(suite, result%scalar_val, 100.0_real64, 1.0e-10_real64) | ||
| 133 | + | ||
| 134 | + ! Test special functions | ||
| 135 | + call add_test_case(suite, 'floor(3.7) = 3') | ||
| 136 | + arg = create_scalar(3.7_real64) | ||
| 137 | + result = eval_special('floor', arg) | ||
| 138 | + call assert_equals(suite, result%scalar_val, 3.0_real64) | ||
| 139 | + | ||
| 140 | + call add_test_case(suite, 'floor(-2.3) = -3') | ||
| 141 | + arg = create_scalar(-2.3_real64) | ||
| 142 | + result = eval_special('floor', arg) | ||
| 143 | + call assert_equals(suite, result%scalar_val, -3.0_real64) | ||
| 144 | + | ||
| 145 | + call add_test_case(suite, 'ceil(3.2) = 4') | ||
| 146 | + arg = create_scalar(3.2_real64) | ||
| 147 | + result = eval_special('ceil', arg) | ||
| 148 | + call assert_equals(suite, result%scalar_val, 4.0_real64) | ||
| 149 | + | ||
| 150 | + call add_test_case(suite, 'ceil(-2.7) = -2') | ||
| 151 | + arg = create_scalar(-2.7_real64) | ||
| 152 | + result = eval_special('ceil', arg) | ||
| 153 | + call assert_equals(suite, result%scalar_val, -2.0_real64) | ||
| 154 | + | ||
| 155 | + call add_test_case(suite, 'round(3.5) = 4') | ||
| 156 | + arg = create_scalar(3.5_real64) | ||
| 157 | + result = eval_special('round', arg) | ||
| 158 | + call assert_equals(suite, result%scalar_val, 4.0_real64) | ||
| 159 | + | ||
| 160 | + call add_test_case(suite, 'round(3.4) = 3') | ||
| 161 | + arg = create_scalar(3.4_real64) | ||
| 162 | + result = eval_special('round', arg) | ||
| 163 | + call assert_equals(suite, result%scalar_val, 3.0_real64) | ||
| 164 | + | ||
| 165 | + call add_test_case(suite, 'factorial(5) = 120') | ||
| 166 | + arg = create_scalar(5.0_real64) | ||
| 167 | + result = eval_special('factorial', arg) | ||
| 168 | + call assert_equals(suite, result%scalar_val, 120.0_real64) | ||
| 169 | + | ||
| 170 | + call add_test_case(suite, 'factorial(0) = 1') | ||
| 171 | + arg = create_scalar(0.0_real64) | ||
| 172 | + result = eval_special('factorial', arg) | ||
| 173 | + call assert_equals(suite, result%scalar_val, 1.0_real64) | ||
| 174 | + | ||
| 175 | + ! Test complex functions | ||
| 176 | + call add_test_case(suite, 'real(3+4i) = 3') | ||
| 177 | + arg = create_complex(3.0_real64, 4.0_real64) | ||
| 178 | + result = eval_complex_functions('real', arg) | ||
| 179 | + call assert_equals(suite, result%scalar_val, 3.0_real64) | ||
| 180 | + | ||
| 181 | + call add_test_case(suite, 'imag(3+4i) = 4') | ||
| 182 | + arg = create_complex(3.0_real64, 4.0_real64) | ||
| 183 | + result = eval_complex_functions('imag', arg) | ||
| 184 | + call assert_equals(suite, result%scalar_val, 4.0_real64) | ||
| 185 | + | ||
| 186 | + call add_test_case(suite, 'abs(3+4i) = 5') | ||
| 187 | + arg = create_complex(3.0_real64, 4.0_real64) | ||
| 188 | + result = eval_complex_functions('cabs', arg) | ||
| 189 | + call assert_equals(suite, result%scalar_val, 5.0_real64) | ||
| 190 | + | ||
| 191 | + call add_test_case(suite, 'arg(1+i) = pi/4') | ||
| 192 | + arg = create_complex(1.0_real64, 1.0_real64) | ||
| 193 | + result = eval_complex_functions('arg', arg) | ||
| 194 | + call assert_near(suite, result%scalar_val, pi/4.0_real64, 1.0e-10_real64) | ||
| 195 | + | ||
| 196 | + ! Run the test suite | ||
| 197 | + call run_test_suite(suite) | ||
| 198 | + | ||
| 199 | +end program test_functions | ||