From 40939b3ebe359b19cc4ee1d9a7b831fada71ce75 Mon Sep 17 00:00:00 2001 From: Hannes Schulze Date: Wed, 3 Jul 2024 15:05:18 +0200 Subject: implement a parser for mathematical expressions --- src/services/quick-answers/meson.build | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 src/services/quick-answers/meson.build (limited to 'src/services/quick-answers/meson.build') diff --git a/src/services/quick-answers/meson.build b/src/services/quick-answers/meson.build new file mode 100644 index 0000000..a1cc573 --- /dev/null +++ b/src/services/quick-answers/meson.build @@ -0,0 +1,5 @@ +foobar_sources += files( + 'math-lexer.c', + 'math-parser.c', + 'math.c', +) -- cgit v1.3.1 From e36217208802c001f1fbc758e5f103cbc4f339a2 Mon Sep 17 00:00:00 2001 From: Hannes Schulze Date: Sun, 3 Nov 2024 21:07:34 +0100 Subject: add unit tests for math --- flake.lock | 12 +-- meson.build | 26 +++--- src/meson.build | 77 ++++++++++++----- src/services/quick-answer-service.c | 15 +++- src/services/quick-answer-service.h | 2 +- src/services/quick-answers/math.c | 142 +++++++++++++++++++++--------- src/services/quick-answers/math.h | 23 ++--- src/services/quick-answers/math.test.c | 153 +++++++++++++++++++++++++++++++++ src/services/quick-answers/meson.build | 4 + subprojects/mutest.wrap | 4 + 10 files changed, 364 insertions(+), 94 deletions(-) create mode 100644 src/services/quick-answers/math.test.c create mode 100644 subprojects/mutest.wrap (limited to 'src/services/quick-answers/meson.build') diff --git a/flake.lock b/flake.lock index 5005946..1ccee2c 100644 --- a/flake.lock +++ b/flake.lock @@ -5,11 +5,11 @@ "systems": "systems" }, "locked": { - "lastModified": 1710146030, - "narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=", + "lastModified": 1726560853, + "narHash": "sha256-X6rJYSESBVr3hBoH0WbKE5KvhPU5bloyZ2L4K60/fPQ=", "owner": "numtide", "repo": "flake-utils", - "rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a", + "rev": "c1dfcf08411b08f6b8615f7d8971a2bfa81d5e8a", "type": "github" }, "original": { @@ -20,11 +20,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1719506693, - "narHash": "sha256-C8e9S7RzshSdHB7L+v9I51af1gDM5unhJ2xO1ywxNH8=", + "lastModified": 1729880355, + "narHash": "sha256-RP+OQ6koQQLX5nw0NmcDrzvGL8HDLnyXt/jHhL1jwjM=", "owner": "nixos", "repo": "nixpkgs", - "rev": "b2852eb9365c6de48ffb0dc2c9562591f652242a", + "rev": "18536bf04cd71abd345f9579158841376fdd0c5a", "type": "github" }, "original": { diff --git a/meson.build b/meson.build index 457286b..f1cdb9d 100644 --- a/meson.build +++ b/meson.build @@ -10,16 +10,22 @@ json_dep = dependency('json-glib-1.0', version: '>=1.8') nm_dep = dependency('libnm', version: '>=1.44') gmp_dep = dependency('gmp', version: '>=6.3') m_dep = meson.get_compiler('c').find_library('m', required : false) -layershell_dep = dependency('gtk4-layer-shell', required: false) -gvc_dep = dependency('gvc', required: false) -if not layershell_dep.found() - layershell_proj = subproject('gtk4-layer-shell') - layershell_dep = layershell_proj.get_variable('gtk_layer_shell') -endif -if not gvc_dep.found() - gvc_proj = subproject('gvc') - gvc_dep = gvc_proj.get_variable('libgvc_dep') -endif +layershell_dep = dependency( + 'gtk4-layer-shell', + fallback: [ 'gtk4-layer-shell', 'gtk_layer_shell' ], + default_options: [ 'default_library=static' ], +) +gvc_dep = dependency( + 'gvc', + fallback: [ 'gvc', 'libgvc_dep' ], + default_options: [ 'default_library=static' ], +) +mutest_dep = dependency( + 'mutest', + fallback: [ 'mutest', 'mutest_dep' ], + default_options: [ 'static=true' ], + required: false, +) subdir('res') subdir('src') diff --git a/src/meson.build b/src/meson.build index 9c6ef09..b9383fb 100644 --- a/src/meson.build +++ b/src/meson.build @@ -1,5 +1,4 @@ foobar_sources = files( - 'main.c', 'application.c', 'utils.c', 'panel.c', @@ -8,36 +7,68 @@ foobar_sources = files( 'control-center.c', 'notification-area.c', ) +foobar_tests = {} subdir('dbus') subdir('services') subdir('widgets') +c_args = [ + '-Wall', + '-Werror', + '-Wswitch-enum', + '-Wswitch-default', + '-Wunused', + '-Wuninitialized', +] + +deps = [ + glib_dep, + gio_dep, + gio_unix_dep, + gtk_dep, + json_dep, + gmp_dep, + nm_dep, + m_dep, + layershell_dep, + gvc_dep, +] + +libfoobar = static_library( + meson.project_name(), + foobar_sources, + include_directories: include_directories('.'), + dependencies: deps, + install: false, + c_args: c_args +) + +libfoobar_dep = declare_dependency( + link_with: libfoobar, + include_directories: include_directories('.'), + dependencies: deps, +) + executable( meson.project_name(), + 'main.c', styles_resource, icons_resource, - foobar_sources, - include_directories: include_directories('.'), - dependencies: [ - glib_dep, - gio_dep, - gio_unix_dep, - gtk_dep, - json_dep, - gmp_dep, - nm_dep, - m_dep, - layershell_dep, - gvc_dep, - ], + dependencies: [ libfoobar_dep ], install: true, - c_args: [ - '-Wall', - '-Werror', - '-Wswitch-enum', - '-Wswitch-default', - '-Wunused', - '-Wuninitialized', - ], + c_args: c_args ) + +if mutest_dep.found() + foreach name, sources : foobar_tests + test( + name, + executable( + 'foobar-test-' + name, + sources, + dependencies: [ libfoobar_dep, mutest_dep ], + ), + ) + endforeach +endif diff --git a/src/services/quick-answer-service.c b/src/services/quick-answer-service.c index 8071998..670a72f 100644 --- a/src/services/quick-answer-service.c +++ b/src/services/quick-answer-service.c @@ -3,6 +3,12 @@ #include "launcher-item.h" #include +// +// FoobarQuickAnswer: +// +// A launcher item representing a quick, one-line answer to the query entered by the user. +// + struct _FoobarQuickAnswer { GObject parent_instance; @@ -48,6 +54,13 @@ G_DEFINE_FINAL_TYPE_WITH_CODE( G_TYPE_OBJECT, G_IMPLEMENT_INTERFACE( FOOBAR_TYPE_LAUNCHER_ITEM, foobar_quick_answer_launcher_item_interface_init ) ) +// +// FoobarQuickAnswerService: +// +// Service providing quick answers for search queries in the launcher (if available). This includes evaluating +// mathematical expressions. +// + struct _FoobarQuickAnswerService { GObject parent_instance; @@ -324,7 +337,7 @@ FoobarQuickAnswer* foobar_quick_answer_service_query_math( gchar const* query ) FoobarMathExpression* expr = foobar_math_parse( tokens, token_count ); if ( expr ) { - foobar_math_expression_print( expr, 0 ); + // foobar_math_expression_print( expr, 0 ); FoobarMathValue val = { 0 }; if ( foobar_math_evaluate( expr, &val ) ) diff --git a/src/services/quick-answer-service.h b/src/services/quick-answer-service.h index 02a8e10..adb0701 100644 --- a/src/services/quick-answer-service.h +++ b/src/services/quick-answer-service.h @@ -16,6 +16,6 @@ G_DECLARE_FINAL_TYPE( FoobarQuickAnswerService, foobar_quick_answer_service, FOO FoobarQuickAnswerService* foobar_quick_answer_service_new ( void ); FoobarQuickAnswer* foobar_quick_answer_service_query ( FoobarQuickAnswerService* self, - gchar const* query ); + gchar const* query ); G_END_DECLS diff --git a/src/services/quick-answers/math.c b/src/services/quick-answers/math.c index 42bc8bd..e813808 100644 --- a/src/services/quick-answers/math.c +++ b/src/services/quick-answers/math.c @@ -310,72 +310,81 @@ void foobar_math_value_negate( FoobarMathValue* value ) } gboolean foobar_math_value_add( - FoobarMathValue lhs, - FoobarMathValue rhs, + FoobarMathValue* lhs, + FoobarMathValue* rhs, FoobarMathValue* out_value ) { - if ( foobar_math_value_is_simple_int( lhs ) && foobar_math_value_is_simple_int( rhs ) ) + if ( foobar_math_value_unify_integers( lhs, rhs ) ) { foobar_math_value_new_int( out_value ); - mpz_add( out_value->int_value.v, lhs.int_value.v, rhs.int_value.v ); + out_value->int_value.decimal_places = lhs->int_value.decimal_places; + mpz_add( out_value->int_value.v, lhs->int_value.v, rhs->int_value.v ); return TRUE; } - long double lhs_float = foobar_math_value_to_float( lhs ); - long double rhs_float = foobar_math_value_to_float( rhs ); + long double lhs_float = foobar_math_value_to_float( *lhs ); + long double rhs_float = foobar_math_value_to_float( *rhs ); foobar_math_value_from_float( lhs_float + rhs_float, out_value ); return TRUE; } gboolean foobar_math_value_sub( - FoobarMathValue lhs, - FoobarMathValue rhs, + FoobarMathValue* lhs, + FoobarMathValue* rhs, FoobarMathValue* out_value ) { - if ( foobar_math_value_is_simple_int( lhs ) && foobar_math_value_is_simple_int( rhs ) ) + if ( foobar_math_value_unify_integers( lhs, rhs ) ) { foobar_math_value_new_int( out_value ); - mpz_sub( out_value->int_value.v, lhs.int_value.v, rhs.int_value.v ); + out_value->int_value.decimal_places = lhs->int_value.decimal_places; + mpz_sub( out_value->int_value.v, lhs->int_value.v, rhs->int_value.v ); return TRUE; } - long double lhs_float = foobar_math_value_to_float( lhs ); - long double rhs_float = foobar_math_value_to_float( rhs ); + long double lhs_float = foobar_math_value_to_float( *lhs ); + long double rhs_float = foobar_math_value_to_float( *rhs ); foobar_math_value_from_float( lhs_float - rhs_float, out_value ); return TRUE; } gboolean foobar_math_value_mul( - FoobarMathValue lhs, - FoobarMathValue rhs, + FoobarMathValue* lhs, + FoobarMathValue* rhs, FoobarMathValue* out_value ) { - if ( foobar_math_value_is_simple_int( lhs ) && foobar_math_value_is_simple_int( rhs ) ) + if ( lhs->type == FOOBAR_MATH_VALUE_INT && rhs->type == FOOBAR_MATH_VALUE_INT ) { foobar_math_value_new_int( out_value ); - mpz_mul( out_value->int_value.v, lhs.int_value.v, rhs.int_value.v ); + out_value->int_value.decimal_places = lhs->int_value.decimal_places + rhs->int_value.decimal_places; + mpz_mul( out_value->int_value.v, lhs->int_value.v, rhs->int_value.v ); return TRUE; } - long double lhs_float = foobar_math_value_to_float( lhs ); - long double rhs_float = foobar_math_value_to_float( rhs ); + long double lhs_float = foobar_math_value_to_float( *lhs ); + long double rhs_float = foobar_math_value_to_float( *rhs ); foobar_math_value_from_float( lhs_float * rhs_float, out_value ); return TRUE; } gboolean foobar_math_value_div( - FoobarMathValue lhs, - FoobarMathValue rhs, + FoobarMathValue* lhs, + FoobarMathValue* rhs, FoobarMathValue* out_value ) { - if ( foobar_math_value_is_simple_int( lhs ) && foobar_math_value_is_simple_int( rhs ) ) + if ( lhs->type == FOOBAR_MATH_VALUE_INT && rhs->type == FOOBAR_MATH_VALUE_INT ) { - if ( mpz_sgn( rhs.int_value.v ) == 0 ) { return FALSE; } + if ( mpz_sgn( rhs->int_value.v ) == 0 ) { return FALSE; } + + if ( rhs->int_value.decimal_places > lhs->int_value.decimal_places ) + { + foobar_math_value_unify_integers( lhs, rhs ); + } foobar_math_value_new_int( out_value ); + out_value->int_value.decimal_places = lhs->int_value.decimal_places - rhs->int_value.decimal_places; mpz_t rem; mpz_init( rem ); - mpz_divmod( out_value->int_value.v, rem, lhs.int_value.v, rhs.int_value.v ); + mpz_divmod( out_value->int_value.v, rem, lhs->int_value.v, rhs->int_value.v ); gboolean result_is_int = mpz_sgn( rem ) == 0; mpz_clear( rem ); @@ -383,8 +392,8 @@ gboolean foobar_math_value_div( foobar_math_value_free( *out_value ); } - long double lhs_float = foobar_math_value_to_float( lhs ); - long double rhs_float = foobar_math_value_to_float( rhs ); + long double lhs_float = foobar_math_value_to_float( *lhs ); + long double rhs_float = foobar_math_value_to_float( *rhs ); long double result = lhs_float / rhs_float; if ( isnanl( result ) || isinfl( result ) ) { return FALSE; } foobar_math_value_from_float( result, out_value ); @@ -392,29 +401,60 @@ gboolean foobar_math_value_div( } gboolean foobar_math_value_pow( - FoobarMathValue lhs, - FoobarMathValue rhs, + FoobarMathValue* lhs, + FoobarMathValue* rhs, FoobarMathValue* out_value ) { - if ( foobar_math_value_is_simple_int( lhs ) && foobar_math_value_is_simple_int( rhs ) && - mpz_fits_ulong_p( rhs.int_value.v ) ) + if ( lhs->type == FOOBAR_MATH_VALUE_INT && + rhs->type == FOOBAR_MATH_VALUE_INT && + rhs->int_value.decimal_places == 0 && + mpz_fits_ulong_p( rhs->int_value.v ) ) { + unsigned long exp = mpz_get_ui( rhs->int_value.v ); foobar_math_value_new_int( out_value ); - mpz_pow_ui( out_value->int_value.v, lhs.int_value.v, mpz_get_ui( rhs.int_value.v ) ); + out_value->int_value.decimal_places = lhs->int_value.decimal_places * exp; + mpz_pow_ui( out_value->int_value.v, lhs->int_value.v, exp ); return TRUE; } - long double lhs_float = foobar_math_value_to_float( lhs ); - long double rhs_float = foobar_math_value_to_float( rhs ); + long double lhs_float = foobar_math_value_to_float( *lhs ); + long double rhs_float = foobar_math_value_to_float( *rhs ); long double result = powl( lhs_float, rhs_float ); if ( isnanl( result ) || isinfl( result ) ) { return FALSE; } foobar_math_value_from_float( result, out_value ); return TRUE; } -gboolean foobar_math_value_is_simple_int( FoobarMathValue value ) +gboolean foobar_math_value_unify_integers( + FoobarMathValue* a, + FoobarMathValue* b ) { - return value.type == FOOBAR_MATH_VALUE_INT && value.int_value.decimal_places == 0; + if ( a->type != FOOBAR_MATH_VALUE_INT || b->type != FOOBAR_MATH_VALUE_INT ) { return FALSE; } + + // Extend the less precise representation so both numbers have the same number of decimal places. + + gsize decimal_places = MAX( a->int_value.decimal_places, b->int_value.decimal_places ); + + mpz_t exp; + mpz_init( exp ); + + if ( decimal_places > a->int_value.decimal_places ) + { + mpz_ui_pow_ui( exp, 10, decimal_places - a->int_value.decimal_places ); + mpz_mul( a->int_value.v, a->int_value.v, exp ); + } + + if ( decimal_places > b->int_value.decimal_places ) + { + mpz_ui_pow_ui( exp, 10, decimal_places - b->int_value.decimal_places ); + mpz_mul( b->int_value.v, b->int_value.v, exp ); + } + + mpz_clear( exp ); + + a->int_value.decimal_places = b->int_value.decimal_places = decimal_places; + + return TRUE; } gchar* foobar_math_value_to_string( FoobarMathValue value ) @@ -423,10 +463,23 @@ gchar* foobar_math_value_to_string( FoobarMathValue value ) { case FOOBAR_MATH_VALUE_INT: { - size_t len = mpz_sizeinbase( value.int_value.v, 10 ); - gchar* result = g_new0( gchar, len + 3 ); // length + null terminator + sign + separator + gsize cap = mpz_sizeinbase( value.int_value.v, 10 ); + cap = MAX( cap, value.int_value.decimal_places + 1 ); // pad with zeroes if necessary + gchar* result = g_new0( gchar, cap + 3 ); // length + null terminator + sign + separator mpz_get_str( result, 10, value.int_value.v ); - len = strlen( result ); + gsize len = strlen( result ); // actual size may be 1 char less (+ len now also includes sign) + gboolean has_sign = result[0] == '-'; + if ( has_sign ) + { + // add the sign back later, makes everything else easier + result += 1; + len -= 1; + } + // pad with leading zeroes if necessary + gsize padded_len = MAX( len, value.int_value.decimal_places + 1 ); + memmove( &result[padded_len - len], result, len + 1 ); + memset( result, '0', padded_len - len ); + len = padded_len; gboolean trailing_zeros = TRUE; gsize decimal_places = value.int_value.decimal_places; gsize actual_decimal_places = decimal_places; @@ -447,6 +500,11 @@ gchar* foobar_math_value_to_string( FoobarMathValue value ) memmove( &result[len + 1 - decimal_places], &result[len - decimal_places], decimal_places + 1 ); result[len - decimal_places] = '.'; } + if ( has_sign ) + { + result -= 1; + len += 1; + } return result; } case FOOBAR_MATH_VALUE_FLOAT: @@ -617,19 +675,19 @@ gboolean foobar_math_evaluate( switch ( expr->operation.o ) { case FOOBAR_MATH_OPERATION_ADD: - success = foobar_math_value_add( lhs, rhs, out_value ); + success = foobar_math_value_add( &lhs, &rhs, out_value ); break; case FOOBAR_MATH_OPERATION_SUB: - success = foobar_math_value_sub( lhs, rhs, out_value ); + success = foobar_math_value_sub( &lhs, &rhs, out_value ); break; case FOOBAR_MATH_OPERATION_MUL: - success = foobar_math_value_mul( lhs, rhs, out_value ); + success = foobar_math_value_mul( &lhs, &rhs, out_value ); break; case FOOBAR_MATH_OPERATION_DIV: - success = foobar_math_value_div( lhs, rhs, out_value ); + success = foobar_math_value_div( &lhs, &rhs, out_value ); break; case FOOBAR_MATH_OPERATION_POW: - success = foobar_math_value_pow( lhs, rhs, out_value ); + success = foobar_math_value_pow( &lhs, &rhs, out_value ); break; default: g_warn_if_reached( ); diff --git a/src/services/quick-answers/math.h b/src/services/quick-answers/math.h index 6bc07fe..c66a2bf 100644 --- a/src/services/quick-answers/math.h +++ b/src/services/quick-answers/math.h @@ -123,22 +123,23 @@ void foobar_math_value_copy ( FoobarMathValue FoobarMathValue* out_value ); void foobar_math_value_free ( FoobarMathValue value ); void foobar_math_value_negate ( FoobarMathValue* value ); -gboolean foobar_math_value_add ( FoobarMathValue lhs, - FoobarMathValue rhs, +gboolean foobar_math_value_add ( FoobarMathValue* lhs, + FoobarMathValue* rhs, FoobarMathValue* out_value ); -gboolean foobar_math_value_sub ( FoobarMathValue lhs, - FoobarMathValue rhs, +gboolean foobar_math_value_sub ( FoobarMathValue* lhs, + FoobarMathValue* rhs, FoobarMathValue* out_value ); -gboolean foobar_math_value_mul ( FoobarMathValue lhs, - FoobarMathValue rhs, +gboolean foobar_math_value_mul ( FoobarMathValue* lhs, + FoobarMathValue* rhs, FoobarMathValue* out_value ); -gboolean foobar_math_value_div ( FoobarMathValue lhs, - FoobarMathValue rhs, +gboolean foobar_math_value_div ( FoobarMathValue* lhs, + FoobarMathValue* rhs, FoobarMathValue* out_value ); -gboolean foobar_math_value_pow ( FoobarMathValue lhs, - FoobarMathValue rhs, +gboolean foobar_math_value_pow ( FoobarMathValue* lhs, + FoobarMathValue* rhs, FoobarMathValue* out_value ); -gboolean foobar_math_value_is_simple_int ( FoobarMathValue value ); +gboolean foobar_math_value_unify_integers ( FoobarMathValue* a, + FoobarMathValue* b ); gchar* foobar_math_value_to_string ( FoobarMathValue value ); long double foobar_math_value_to_float ( FoobarMathValue value ); gboolean foobar_math_evaluate ( FoobarMathExpression const* expr, diff --git a/src/services/quick-answers/math.test.c b/src/services/quick-answers/math.test.c new file mode 100644 index 0000000..416b576 --- /dev/null +++ b/src/services/quick-answers/math.test.c @@ -0,0 +1,153 @@ +#include "services/quick-answers/math.h" +#include + +#define SERIALIZATION_TEST( value, identifier ) \ + static void serialization_value_##identifier##_spec( void ) \ + { \ + FoobarMathValue val; \ + \ + gchar const input[] = #value; \ + mutest_expect( \ + "parse result", \ + mutest_bool_value( foobar_math_value_from_string( input, sizeof(input) - 1, &val) ), \ + mutest_to_be_true, \ + NULL ); \ + g_autofree gchar* output = foobar_math_value_to_string( val ); \ + mutest_expect( \ + "string representation", \ + mutest_string_value( output ), \ + mutest_to_be, \ + input, \ + NULL ); \ + \ + foobar_math_value_free( val ); \ + } + +SERIALIZATION_TEST( 0, 0 ) +SERIALIZATION_TEST( 1, 1 ) +SERIALIZATION_TEST( -1, neg_1 ) +SERIALIZATION_TEST( 1.2, 1_2 ) +SERIALIZATION_TEST( -1.2, neg_1_2 ) +SERIALIZATION_TEST( 1.30002, 1_30002 ) +SERIALIZATION_TEST( -1.30002, neg_1_30002 ) +SERIALIZATION_TEST( 0.2, 0_2 ) +SERIALIZATION_TEST( -0.2, neg_0_2 ) +SERIALIZATION_TEST( 0.00002, 0_00002 ) +SERIALIZATION_TEST( -0.00002, neg_0_00002 ) +SERIALIZATION_TEST( 123, 123 ) +SERIALIZATION_TEST( 123.456, 123_456 ) +SERIALIZATION_TEST( -123, neg_123 ) +SERIALIZATION_TEST( -123.456, neg_123_456 ) + +#undef SERIALIZATION_TEST + +static void serialization_suite( void ) +{ + mutest_it( "value 0", serialization_value_0_spec ); + mutest_it( "value 1", serialization_value_1_spec ); + mutest_it( "value -1", serialization_value_neg_1_spec ); + mutest_it( "value 1.2", serialization_value_1_2_spec ); + mutest_it( "value -1.2", serialization_value_neg_1_2_spec ); + mutest_it( "value 1.30002", serialization_value_1_30002_spec ); + mutest_it( "value -1.30002", serialization_value_neg_1_30002_spec ); + mutest_it( "value 0.2", serialization_value_0_2_spec ); + mutest_it( "value -0.2", serialization_value_neg_0_2_spec ); + mutest_it( "value 0.00002", serialization_value_0_00002_spec ); + mutest_it( "value -0.00002", serialization_value_neg_0_00002_spec ); + mutest_it( "value 123", serialization_value_123_spec ); + mutest_it( "value 123.456", serialization_value_123_456_spec ); + mutest_it( "value -123", serialization_value_neg_123_spec ); + mutest_it( "value -123.456", serialization_value_neg_123_456_spec ); +} +#define OPERATION_TEST( op, value_a, value_b, value_res, identifier ) \ + static void operation_##identifier##_spec( void ) \ + { \ + FoobarMathValue a, b, res; \ + \ + gchar const input_a[] = #value_a; \ + gchar const input_b[] = #value_b; \ + mutest_expect( \ + "input a parsed", \ + mutest_bool_value( foobar_math_value_from_string( input_a, sizeof(input_a) - 1, &a) ), \ + mutest_to_be_true, \ + NULL ); \ + mutest_expect( \ + "input b parsed", \ + mutest_bool_value( foobar_math_value_from_string( input_b, sizeof(input_b) - 1, &b) ), \ + mutest_to_be_true, \ + NULL ); \ + foobar_math_value_##op( &a, &b, &res ); \ + g_autofree gchar* output = foobar_math_value_to_string( res ); \ + mutest_expect( \ + "result", \ + mutest_string_value( output ), \ + mutest_to_be, \ + #value_res, \ + NULL ); \ + \ + foobar_math_value_free( a ); \ + foobar_math_value_free( b ); \ + foobar_math_value_free( res ); \ + } + +OPERATION_TEST( add, 123, 456, 579, 123_plus_456) +OPERATION_TEST( add, 123.456, 11, 134.456, 123_456_plus_11) +OPERATION_TEST( add, 11, 123.456, 134.456, 11_plus_123_456) +OPERATION_TEST( sub, 123, 456, -333, 123_minus_456) +OPERATION_TEST( sub, 123.456, 11, 112.456, 123_456_minus_11) +OPERATION_TEST( sub, 11, 123.456, -112.456, 11_minus_123_456) +OPERATION_TEST( mul, 123, 3, 369, 123_times_3 ) +OPERATION_TEST( mul, 12, 0.05, 0.6, 12_times_0_05 ) +OPERATION_TEST( mul, 0.05, 12, 0.6, 0_05_times_12 ) +OPERATION_TEST( div, 369, 3, 123, 369_div_3 ) +OPERATION_TEST( div, 369, 2, 184.5, 369_div_2 ) +OPERATION_TEST( div, 12, 0.05, 240, 12_div_0_05 ) +OPERATION_TEST( div, 0.08, 4, 0.02, 0_08_div_4 ) +OPERATION_TEST( pow, 2, 3, 8, 2_pow_3 ) +OPERATION_TEST( pow, 4, 0.5, 2, 4_pow_0_5 ) + +#undef OPERATION_TEST + +static void addition_suite( void ) +{ + mutest_it( "calculate 123 + 456", operation_123_plus_456_spec ); + mutest_it( "calculate 123.456 + 11", operation_123_456_plus_11_spec ); + mutest_it( "calculate 11 + 123.456", operation_11_plus_123_456_spec ); +} + +static void subtraction_suite( void ) +{ + mutest_it( "calculate 123 - 456", operation_123_minus_456_spec ); + mutest_it( "calculate 123.456 - 11", operation_123_456_minus_11_spec ); + mutest_it( "calculate 11 - 123.456", operation_11_minus_123_456_spec ); +} + +static void multiplication_suite( void ) +{ + mutest_it( "calculate 123 * 3", operation_123_times_3_spec ); + mutest_it( "calculate 12 * 0.05", operation_12_times_0_05_spec ); + mutest_it( "calculate 0.05 * 12", operation_0_05_times_12_spec ); +} + +static void division_suite( void ) +{ + mutest_it( "calculate 369 / 3", operation_369_div_3_spec ); + mutest_it( "calculate 369 / 2", operation_369_div_2_spec ); + mutest_it( "calculate 12 / 0.05", operation_12_div_0_05_spec ); + mutest_it( "calculate 0.08 / 4", operation_0_08_div_4_spec ); +} + +static void power_suite( void ) +{ + mutest_it( "calculate 2 ^ 3", operation_2_pow_3_spec ); + mutest_it( "calculate 4 ^ 0.5", operation_4_pow_0_5_spec ); +} + +MUTEST_MAIN( + mutest_describe( "Serialization", serialization_suite ); + mutest_describe( "Addition", addition_suite ); + mutest_describe( "Subtraction", subtraction_suite ); + mutest_describe( "Multiplication", multiplication_suite ); + mutest_describe( "Division", division_suite ); + mutest_describe( "Power", power_suite ); +) diff --git a/src/services/quick-answers/meson.build b/src/services/quick-answers/meson.build index a1cc573..ae468c2 100644 --- a/src/services/quick-answers/meson.build +++ b/src/services/quick-answers/meson.build @@ -3,3 +3,7 @@ foobar_sources += files( 'math-parser.c', 'math.c', ) + +foobar_tests += { + 'math': files('math.test.c'), +} diff --git a/subprojects/mutest.wrap b/subprojects/mutest.wrap new file mode 100644 index 0000000..6e99973 --- /dev/null +++ b/subprojects/mutest.wrap @@ -0,0 +1,4 @@ +[wrap-git] +url = https://github.com/ebassi/mutest.git +revision = 18a20071773f7c4b75e82a931ef9b916b273b3e5 +depth = 1 -- cgit v1.3.1