6.10.94-stable

Testing with GreyCat

GreyCat test command

Like any other softwares, GreyCat based project need to be test

GreyCat natively integrates a framework for

  • unit tests
  • integration tests
  • api tests

tests can be executed through the test command of GreyCat CLI

greycat test
model_test
        customer_validate_creation_time: ok (11 us)
        customer_validate_connection_time: ok (1 us)
api_test
        prepare: ok (10 us)
        api_validate_customer_retrieve: ok (2 us)

tests success: 4, failed: 0

Test modules naming convention

every GreyCat tests should be named with a _test.gcl suffix

a classical project tree view will look like this

├── project.gcl
├── src
│   ├── api.gcl
│   ├── model.gcl
│   └── model_test.gcl
└── test
    └── api_test.gcl

  • src directory contains model and api sources and eventually unit tests
  • test contains every remaining tests sources (integration and api tests usually)
  • project root is defined by project.gcl at the root of the project repository and contains @include directives

@include("src");
@include("test");

Test function naming convention

every function decorated by a @test pragma will be considered as a test unit by GreyCat

@test
fn my_test_function(){
    Assert::isNull(null); // <- probably valid assertion...
}

every Greycat module can integrate several tests directly within the sources

//customer_test.gcl
@test 
fn test1(){

}
@test 
fn test2(){

}

execution order of all tests will follow definition order within one module


Unit tests

  • model.gcl
type Customer {
    name: String;
    creation_time: time;
    last_connection_time: time?;
    static fn new(name: String): Customer{
        return Customer {name: name, creation_time: time::now()};
    }
}

var customers: nodeIndex<String,Customer>?;
  • model_test.gcl
use model;
use util;

@test
fn customer_validate_creation_time(){
    var c = Customer::new("John Doe");
    Assert::equals(c.creation_time.to(DurationUnit::seconds),time::now().to(DurationUnit::seconds));
}

@test
fn customer_validate_connection_time(){
    var c = Customer::new("John Doe");
    Assert::isNull(c.last_connection_time);
}

API tests (same for integration tests)

  • api.gcl
use model;
@expose
fn customer(name: String) : Customer? {
    return customers?.get(name);
}
  • api_test.gcl
use model;
use api; // this statement makes all API accessible as functions
use util;

@test
fn prepare(){ // must be first in module, prepare all data for api testing
    customers ?= nodeIndex<String,Customer>::new();
    var c = Customer::new("John Doe"); 
    customers.set(c.name, c);
}

@test
fn api_validate_customer_retrieve(){
    var c = customer("John Doe");
    Assert::equals(c.name, "John Doe");
}

GreyCat test command return code

GreyCat test command return 0 in case of success (CI script friendly)

greycat test
echo $?
> 0

and non zero in case of failure

@test
fn fail(){ throw "very bad";}
greycat test
model_test
        customer_validate_creation_time: ok (15 us)
        customer_validate_connection_time: ok (1 us)
api_test
        prepare: ok (9 us)
        api_validate_customer_retrieve: ok (2 us)
        fail: {"_type":"core.Error","code":{"_type":"core.ErrorCode","field":"throw"},"value":"very bad","stack":["fail (./test/api_test.gcl:20:22)"]}

tests success: 4, failed: 1
echo $?
> 41

Test vs build

GreyCat automatically remove all tests and associated symbols from final build program

there the following command

greycat build 

will produce a project.gcp file

├── project.gcl
├── project.gcp

but the next command

cat project.gcp | grep api_validate

will print nothing, even string literals are dropped by GreyCat build for safety reasons

Assert type

/// Assert is mainly used for testing purposes.
/// It verifies that assertions you make on the state of your data is correct, or throws an [Error](../core/#Error).
type Assert {
  /// Verifies that `a` is equal to `b`, throws an error if not. `a` and `b` can be of any type.
  static native fn equals(a: any?, b: any?);

  /// Verifies that `a` is equal to `b`, throws an error if not. `a` and `b` must be floats.
  static native fn equalsd(a: float, b: float, epsilon: float);

  /// Verifies that `a` is equal to `b`, throws an error if not. `a` and `b` must be tensors.
  static native fn equalst(a: Tensor, b: Tensor, epsilon: float);

  /// Verifies that `v` is true, throws an error if not.
  static native fn isTrue(v: bool);

  /// Verifies that `v` is false, throws an error if not.
  static native fn isFalse(v: bool);

  /// Verifies that `v` is null, throws an error if not.
  static native fn isNull(v: any?);

  /// Verifies that `v` is not null, throws an error if not.
  static native fn isNotNull(v: any?);

  // Verifies that `v` is not null, throws an error if not.")
  //static native fn fail(f: function);
}