Skip to content

Commit

Permalink
* ficus unit tests: the beginning
Browse files Browse the repository at this point in the history
* added functions to measure time
* a few minor bug fixes, as usual
  • Loading branch information
vpisarev committed Jan 28, 2021
1 parent 004b108 commit 86993e9
Show file tree
Hide file tree
Showing 10 changed files with 282 additions and 3 deletions.
1 change: 1 addition & 0 deletions lib/Builtins.fx
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ operator + (l1: 't list, l2: 't list)
}
}

pure fun string(a: exn): string = ccode { return fx_exn_to_string(a, fx_result) }
fun string(a: bool) = if a {"true"} else {"false"}
pure fun string(a: int): string = ccode { return fx_itoa(a, fx_result) }
pure fun string(a: uint8): string = ccode { return fx_itoa(a, fx_result) }
Expand Down
4 changes: 3 additions & 1 deletion lib/String.fx
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,10 @@ pure nothrow fun endswith(s: string, suffix: string): bool = ccode
pure nothrow fun find(s: string, part: string): int = ccode
{
int_ i, sz1 = s->length, sz2 = part->length, l = sz1 - sz2 + 1;
if (sz2 == 0 || l <= 0)
if (sz2 == 0)
return 0;
if (l <= 0)
return -1;
for( i = 0; i < l; i++ ) {
if( s->data[i] == part->data[0] &&
memcmp(s->data + i, part->data, sz2*sizeof(part->data[0])) == 0 )
Expand Down
9 changes: 9 additions & 0 deletions lib/Sys.fx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/*
This file is a part of ficus language project.
See ficus/LICENSE for the licensing terms
*/

// Various system services

pure nothrow fun getTickCount(): int64 = ccode { return fx_tickcount() }
pure nothrow fun getTickFrequency(): double = ccode { return fx_tickfreq() }
5 changes: 5 additions & 0 deletions runtime/ficus/ficus.h
Original file line number Diff line number Diff line change
Expand Up @@ -659,6 +659,11 @@ void fx_file_destructor(void* ptr);

fx_cptr_t fx_get_stdstream(int);

////////////////////// Various useful system API ///////////////////

int64_t fx_tickcount(void);
double fx_tickfreq(void);

////////////////////////// Regular expressions /////////////////////

typedef fx_cptr_t fx_regex_t;
Expand Down
1 change: 1 addition & 0 deletions runtime/ficus/impl/ficus.impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -500,5 +500,6 @@ int fx_make_cptr(void* ptr, fx_free_t free_f, fx_cptr_t* fx_result)
#include "ficus/impl/file.impl.h"
#include "ficus/impl/string.impl.h"
#include "ficus/impl/regex.impl.h"
#include "ficus/impl/system.impl.h"

#endif
70 changes: 70 additions & 0 deletions runtime/ficus/impl/system.impl.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/*
This file is a part of ficus language project.
See ficus/LICENSE for the licensing terms
*/

#ifndef __FICUS_SYSTEM_IMPL_H__
#define __FICUS_SYSTEM_IMPL_H__

#if defined _WIN32 || defined WINCE
#ifndef _WIN32_WINNT // This is needed for the declaration of TryEnterCriticalSection in winbase.h with Visual Studio 2005 (and older?)
#define _WIN32_WINNT 0x0400 // http://msdn.microsoft.com/en-us/library/ms686857(VS.85).aspx
#endif
#include <windows.h>
#elif defined __MACH__ && defined __APPLE__
#include <mach/mach.h>
#include <mach/mach_time.h>
#else
#include <unistd.h>
#endif

#ifdef __cplusplus
extern "C" {
#endif

int64_t fx_tickcount(void)
{
#if defined _WIN32 || defined WINCE
LARGE_INTEGER counter;
QueryPerformanceCounter( &counter );
return (int64_t)counter.QuadPart;
#elif defined __linux || defined __linux__
struct timespec tp;
clock_gettime(CLOCK_MONOTONIC, &tp);
return (int64)tp.tv_sec*1000000000 + tp.tv_nsec;
#elif defined __MACH__ && defined __APPLE__
return (int64_t)mach_absolute_time();
#else
struct timeval tv;
gettimeofday(&tv, NULL);
return (int64_t)tv.tv_sec*1000000 + tv.tv_usec;
#endif
}

double fx_tickfreq(void)
{
#if defined _WIN32 || defined WINCE
LARGE_INTEGER freq;
QueryPerformanceFrequency(&freq);
return (double)freq.QuadPart;
#elif defined __linux || defined __linux__
return 1e9;
#elif defined __MACH__ && defined __APPLE__
static double freq = 0;
if( freq == 0 )
{
mach_timebase_info_data_t sTimebaseInfo;
mach_timebase_info(&sTimebaseInfo);
freq = sTimebaseInfo.denom*1e9/sTimebaseInfo.numer;
}
return freq;
#else
return 1e6;
#endif
}

#ifdef __cplusplus
}
#endif

#endif
2 changes: 1 addition & 1 deletion src/compiler.ml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ let make_lexer fname =
let prev_lnum = ref 0 in
(* the standard preamble *)
let tokenbuf = (if bare_name = "Builtins" then ref [] else
let from_builtins_import_all = [Parser.FROM; Parser.B_IDENT "Builtins"; Parser.IMPORT; Parser.STAR; Parser.SEMICOLON] in
let from_builtins_import_all = [Parser.FROM; Parser.B_IDENT "Builtins"; Parser.IMPORT; Parser.B_STAR; Parser.SEMICOLON] in
let import_list = if bare_name = "List" then []
else [Parser.B_IMPORT; Parser.B_IDENT "List"; Parser.SEMICOLON] in
let import_string = if bare_name = "List" || bare_name = "String" then []
Expand Down
2 changes: 1 addition & 1 deletion src/parser.mly
Original file line number Diff line number Diff line change
Expand Up @@ -279,7 +279,7 @@ top_level_exp:
[DirImport ((List.map (fun (a, b) ->
let a1 = add_to_imported_modules a (pos0, pos1) in (a1, b)) $2), curr_loc())]
}
| FROM dot_ident any_import STAR
| FROM dot_ident any_import B_STAR
{
let (pos0, pos1) = (Parsing.symbol_start_pos(), Parsing.symbol_end_pos()) in
let a = get_id $2 in
Expand Down
180 changes: 180 additions & 0 deletions test/UTest.fx
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
/*
This file is a part of ficus language project.
See ficus/LICENSE for the licensing terms
*/

/* Unit test system for Ficus */
import Sys

exception TestAssertError
exception TestFailure: string

type test_info_t =
{
name: string,
f: void->void
}

type test_rng_t =
{
state: uint64 ref
}

fun test_rng_int(rng: test_rng_t, a: int, b: int)
{
val s = *rng.state
val s = (s :> uint32) * 4197999714u64 + (s >> 32)
val (a, b) = (min(a, b), max(a, b))
val diff = b - a
val x = (s :> uint32) % diff + a
*rng.state = s
(x :> int)
}

fun test_rng_copy(rng: test_rng_t)
{
test_rng_t {state = ref(*rng.state)}
}

type test_state_t =
{
currstatus: bool,
rng: test_rng_t
}

var g_test_all_registered = ([]: test_info_t list)
val g_test_rng0 = test_rng_t {state=ref(0x123456789u64)}

fun test_init_state() = test_state_t {
currstatus=true,
rng=test_rng_copy(g_test_rng0)
}

fun test_init_state_before_test() = test_init_state()

var g_test_state = test_init_state()

fun TEST(name: string, f: void->void)
{
g_test_all_registered = (test_info_t {name=name, f=f}) :: g_test_all_registered
}

fun test_nomsg(): string = ""
fun test_msg(s: string) = (fun() {s})

fun ASSERT(c: bool, msg: void->string)
{
if(!c) {
val m = msg()
if (m == "") {
println(f"Error: Assertion failed.")
} else {
println(f"Error: Assertion failed: {m}")
}
throw TestAssertError
}
}

fun ASSERT(c: bool) = ASSERT(c, test_msg(""))

fun test_failed_assert_cmp(a: 't, b: 't, op: string)
{
println("Error: Assertion \"computed:'{a}' {op} reference:'{b}'\" failed.")
throw TestAssertError
}

fun ASSERT_EQ(a: 't, b: 't) = if a == b {} else {test_failed_assert_cmp(a, b, "==")}
fun ASSERT_NE(a: 't, b: 't) = if a != b {} else {test_failed_assert_cmp(a, b, "!=")}
fun ASSERT_LT(a: 't, b: 't) = if a < b {} else {test_failed_assert_cmp(a, b, "<")}
fun ASSERT_LE(a: 't, b: 't) = if a <= b {} else {test_failed_assert_cmp(a, b, "<=")}
fun ASSERT_GT(a: 't, b: 't) = if a > b {} else {test_failed_assert_cmp(a, b, ">")}
fun ASSERT_GE(a: 't, b: 't) = if a >= b {} else {test_failed_assert_cmp(a, b, ">=")}

fun ASSERT_NEAR(a: 't, b: 't, eps: 't) =
if b - eps <= a <= b + eps {} else {
println("Error: Assertion 'computed:{a} ≈ reference:{b}' with eps={eps} failed.")
throw TestAssertError
}

fun EXPECT(c: bool, msg: void->string)
{
if(!c) {
val m = msg()
if (m == "") {
println(f"Error: Expected: true, Computed: false.")
} else {
println(f"Error: {m}.")
}
g_test_state.currstatus = false
}
}

fun test_failed_expect_cmp(a: 't, b: 't, op: string)
{
println("Error: Expected \"computed:'{a}' {op} reference:'{b}'\".")
g_test_state.currstatus = false
}

fun EXPECT_EQ(a: 't, b: 't) = if a == b {} else {test_failed_expect_cmp(a, b, "==")}
fun EXPECT_NE(a: 't, b: 't) = if a != b {} else {test_failed_expect_cmp(a, b, "!=")}
fun EXPECT_LT(a: 't, b: 't) = if a < b {} else {test_failed_expect_cmp(a, b, "<")}
fun EXPECT_LE(a: 't, b: 't) = if a <= b {} else {test_failed_expect_cmp(a, b, "<=")}
fun EXPECT_GT(a: 't, b: 't) = if a > b {} else {test_failed_expect_cmp(a, b, ">")}
fun EXPECT_GE(a: 't, b: 't) = if a >= b {} else {test_failed_expect_cmp(a, b, ">=")}

fun EXPECT_NEAR(a: 't, b: 't, eps: 't) =
if b - eps <= a <= b + eps {} else {
println("Error: Expected \"computed:'{a}' ≈ reference:'{b}'\" with eps={eps}.")
g_test_state.currstatus = false
}

fun test_run_all(filter:string)
{
val (startswithstar, filter) = if filter.startswith("*") {(true, filter[1:])} else {(false, filter)}
val (endswithstar, filter) = if filter.endswith("*") {(true, filter[:-1])} else {(false, filter)}
if filter.find("*") >= 0 {
throw TestFailure("test filter with '*' inside is currently unsupported")
}
val ts_scale = 1000./Sys.getTickFrequency()
var nexecuted = 0
val ts0_start = Sys.getTickCount()
val fold failed = ([]: string list) for t <- g_test_all_registered.rev() {
val name = t.name
val matches =
filter == "" ||
name == filter ||
(startswithstar && name.endswith(filter)) ||
(endswithstar && name.startswith(filter)) ||
(startswithstar && endswithstar && name.find(filter) != -1)
if (!matches) {failed}
else {
nexecuted += 1
println(f"[ RUN ] {name}")
g_test_state = test_init_state_before_test()
val ts_start = Sys.getTickCount()
try {
t.f()
} catch {
| TestAssertError =>
g_test_state.currstatus = false
| e =>
println("Exception {e} occured.")
g_test_state.currstatus = false
}
val ts_end = Sys.getTickCount()
val ok = g_test_state.currstatus
val ok_fail = if ok {"[ OK ]"} else {"[ FAIL ]"}
println(f"{ok_fail} {name} ({(ts_end - ts_start)*ts_scale} ms)\n")
if ok {failed} else {name :: failed}
}
}
val ts0_end = Sys.getTickCount()
val nfailed = failed.length()
val npassed = nexecuted - nfailed
println(f"[==========] {nexecuted} test(s) ran ({(ts0_end - ts0_start)*ts_scale} ms total)\n")
println(f"[ PASSED ] {npassed} test(s)")
if nfailed > 0 {
println(f"[ FAILED ] {nfailed} test(s):")
for i <- failed.rev() {println(i)}
}
}
11 changes: 11 additions & 0 deletions test/test_basic.fx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
from UTest import *

TEST("Basic.literals", fun() {
val a = 1
val b = -1
val c = 3.14
EXPECT_EQ(a+b, 0)
EXPECT_NEAR(c-0.14, 3.0, 0.001)
})

test_run_all("")

0 comments on commit 86993e9

Please sign in to comment.