kunit: added InSequence implementation
- added prerequisite checking system
- added macro for defining sequences
- added tests for InSequence behaviour
Change-Id: Iada5aa9ebac8403f0a948f4335e4b0962cc468f9
Signed-off-by: Felix Guo <felixguo@google.com>
diff --git a/include/test/mock.h b/include/test/mock.h
index fc209f9..b104fd6 100644
--- a/include/test/mock.h
+++ b/include/test/mock.h
@@ -77,6 +77,13 @@
struct mock_matcher *matcher;
struct mock_method *method;
int times_called;
+ /* internal list of prerequisites */
+ struct list_head prerequisites;
+};
+
+struct mock_expectation_prereq_entry {
+ struct mock_expectation *expectation;
+ struct list_head node;
};
struct mock_method {
@@ -333,6 +340,30 @@
return ActionOnMatch(expectation, return_action);
}
+/**
+ * InSequence() - defines an order for expectations to be matched
+ * @test: the test, used for internal resource allocations
+ * @first: the first &struct mock_expectation in the sequence
+ * @...: the rest of the expectations in order following
+ *
+ * Example:
+ *
+ * .. code-block:: c
+ *
+ * struct mock_expectation *a = EXPECT_CALL(...);
+ * struct mock_expectation *b = EXPECT_CALL(...);
+ * struct mock_expectation *c = EXPECT_CALL(...);
+ *
+ * InSequence(test, a, b, c);
+ *
+ * Return:
+ * 0 if everything was successful, otherwise a memory allocation error
+ */
+#define InSequence(test, first, ...) \
+ mock_in_sequence(test, first, __VA_ARGS__, 0)
+
+int mock_in_sequence(struct test *test, struct mock_expectation *first, ...);
+
#define mock_get_ctrl_internal(mock_object) (&(mock_object)->ctrl)
#define mock_get_ctrl(mock_object) mock_get_ctrl_internal(mock_object)
diff --git a/test/mock-test.c b/test/mock-test.c
index 1281473..83e43ce 100644
--- a/test/mock-test.c
+++ b/test/mock-test.c
@@ -415,6 +415,161 @@
mock_validate_expectations(mock);
}
+
+static void mock_stub(int a) { }
+
+/* Common references for InSequence tests */
+static int param_len = 1;
+static const char * const param_type[] = {"int"};
+
+static const void *a_params[] = { &(int){1} };
+static const void *b_params[] = { &(int){2} };
+static const void *c_params[] = { &(int){3} };
+
+/* Simple test of InSequence, a -> b -> c */
+static void mock_test_in_sequence_simple_pass(struct test *test)
+{
+ struct mock_test_context *ctx = test->priv;
+ struct MOCK(test) *mock_test = ctx->mock_test;
+ struct test *trgt = mock_get_trgt(mock_test);
+ struct mock *mock = ctx->mock;
+
+ struct mock_param_matcher *a_matchers[] = { int_eq(trgt, 1) };
+ struct mock_param_matcher *b_matchers[] = { int_eq(trgt, 2) };
+ struct mock_param_matcher *c_matchers[] = { int_eq(trgt, 3) };
+
+ struct mock_expectation *c = mock_add_matcher(mock, "c", mock_stub,
+ c_matchers, param_len);
+ struct mock_expectation *b = mock_add_matcher(mock, "b", mock_stub,
+ b_matchers, param_len);
+ struct mock_expectation *a = mock_add_matcher(mock, "a", mock_stub,
+ a_matchers, param_len);
+
+ InSequence(test, a, b, c);
+
+ Never(EXPECT_CALL(fail(mock_get_ctrl(mock_test), any(test))));
+
+ mock->do_expect(mock, "a", mock_stub, param_type, a_params, param_len);
+ mock->do_expect(mock, "b", mock_stub, param_type, b_params, param_len);
+ mock->do_expect(mock, "c", mock_stub, param_type, c_params, param_len);
+
+ mock_validate_expectations(mock);
+}
+
+static void mock_test_in_sequence_simple_fail(struct test *test)
+{
+ struct mock_test_context *ctx = test->priv;
+ struct MOCK(test) *mock_test = ctx->mock_test;
+ struct test *trgt = mock_get_trgt(mock_test);
+ struct mock *mock = ctx->mock;
+
+ struct mock_param_matcher *a_matchers[] = { int_eq(trgt, 1) };
+ struct mock_param_matcher *b_matchers[] = { int_eq(trgt, 2) };
+ struct mock_param_matcher *c_matchers[] = { int_eq(trgt, 3) };
+
+ struct mock_expectation *c = mock_add_matcher(mock, "c", mock_stub,
+ c_matchers, param_len);
+ struct mock_expectation *b = mock_add_matcher(mock, "b", mock_stub,
+ b_matchers, param_len);
+ struct mock_expectation *a = mock_add_matcher(mock, "a", mock_stub,
+ a_matchers, param_len);
+
+ InSequence(test, a, b, c);
+
+ EXPECT_CALL(fail(mock_get_ctrl(mock_test), any(test)));
+
+ mock->do_expect(mock, "a", mock_stub, param_type, a_params, param_len);
+ mock->do_expect(mock, "c", mock_stub, param_type, c_params, param_len);
+ mock->do_expect(mock, "b", mock_stub, param_type, b_params, param_len);
+}
+
+/* More complex test of InSequence on two chains in v formation:
+ * a -> c
+ * b -> c
+ */
+static void mock_test_in_sequence_abc_success(struct test *test)
+{
+ struct mock_test_context *ctx = test->priv;
+ struct MOCK(test) *mock_test = ctx->mock_test;
+ struct test *trgt = mock_get_trgt(mock_test);
+ struct mock *mock = ctx->mock;
+
+ struct mock_param_matcher *a_matchers[] = { int_eq(trgt, 1) };
+ struct mock_param_matcher *b_matchers[] = { int_eq(trgt, 2) };
+ struct mock_param_matcher *c_matchers[] = { int_eq(trgt, 3) };
+
+ struct mock_expectation *c = mock_add_matcher(mock, "c", mock_stub,
+ c_matchers, param_len);
+ struct mock_expectation *b = mock_add_matcher(mock, "b", mock_stub,
+ b_matchers, param_len);
+ struct mock_expectation *a = mock_add_matcher(mock, "a", mock_stub,
+ a_matchers, param_len);
+
+ InSequence(test, a, c);
+ InSequence(test, b, c);
+
+ Never(EXPECT_CALL(fail(mock_get_ctrl(mock_test), any(test))));
+
+ mock->do_expect(mock, "a", mock_stub, param_type, a_params, param_len);
+ mock->do_expect(mock, "b", mock_stub, param_type, b_params, param_len);
+ mock->do_expect(mock, "c", mock_stub, param_type, c_params, param_len);
+}
+
+static void mock_test_in_sequence_bac_success(struct test *test)
+{
+ struct mock_test_context *ctx = test->priv;
+ struct MOCK(test) *mock_test = ctx->mock_test;
+ struct test *trgt = mock_get_trgt(mock_test);
+ struct mock *mock = ctx->mock;
+
+ struct mock_param_matcher *a_matchers[] = { int_eq(trgt, 1) };
+ struct mock_param_matcher *b_matchers[] = { int_eq(trgt, 2) };
+ struct mock_param_matcher *c_matchers[] = { int_eq(trgt, 3) };
+
+ struct mock_expectation *c = mock_add_matcher(mock, "c", mock_stub,
+ c_matchers, param_len);
+ struct mock_expectation *b = mock_add_matcher(mock, "b", mock_stub,
+ b_matchers, param_len);
+ struct mock_expectation *a = mock_add_matcher(mock, "a", mock_stub,
+ a_matchers, param_len);
+
+ InSequence(test, a, c);
+ InSequence(test, b, c);
+
+ Never(EXPECT_CALL(fail(mock_get_ctrl(mock_test), any(test))));
+
+ mock->do_expect(mock, "b", mock_stub, param_type, b_params, param_len);
+ mock->do_expect(mock, "a", mock_stub, param_type, a_params, param_len);
+ mock->do_expect(mock, "c", mock_stub, param_type, c_params, param_len);
+}
+
+static void mock_test_in_sequence_no_a_fail(struct test *test)
+{
+ struct mock_test_context *ctx = test->priv;
+ struct MOCK(test) *mock_test = ctx->mock_test;
+ struct test *trgt = mock_get_trgt(mock_test);
+ struct mock *mock = ctx->mock;
+
+ struct mock_param_matcher *a_matchers[] = { int_eq(trgt, 1) };
+ struct mock_param_matcher *b_matchers[] = { int_eq(trgt, 2) };
+ struct mock_param_matcher *c_matchers[] = { int_eq(trgt, 3) };
+
+ struct mock_expectation *c = mock_add_matcher(mock, "c", mock_stub,
+ c_matchers, param_len);
+ struct mock_expectation *b = mock_add_matcher(mock, "b", mock_stub,
+ b_matchers, param_len);
+ struct mock_expectation *a = mock_add_matcher(mock, "a", mock_stub,
+ a_matchers, param_len);
+
+ InSequence(test, a, c);
+ InSequence(test, b, c);
+
+ EXPECT_CALL(fail(mock_get_ctrl(mock_test), any(test)));
+
+ mock->do_expect(mock, "b", mock_stub, param_type, b_params, param_len);
+ mock->do_expect(mock, "c", mock_stub, param_type, c_params, param_len);
+}
+
void *do_mocked_fail(struct mock_action *this, const void **params, int len)
{
static const int ret;
@@ -471,6 +626,11 @@
TEST_CASE(mock_test_naggy_no_matching_expectations_fail),
TEST_CASE(mock_test_nice_no_matching_expectations_fail),
TEST_CASE(mock_test_validate_clears_expectations),
+ TEST_CASE(mock_test_in_sequence_simple_pass),
+ TEST_CASE(mock_test_in_sequence_simple_fail),
+ TEST_CASE(mock_test_in_sequence_abc_success),
+ TEST_CASE(mock_test_in_sequence_bac_success),
+ TEST_CASE(mock_test_in_sequence_no_a_fail),
{},
};
diff --git a/test/mock.c b/test/mock.c
index 8928de8..5d44f71 100644
--- a/test/mock.c
+++ b/test/mock.c
@@ -38,29 +38,39 @@
const void **params,
int len);
+static bool mock_is_expectation_satisfied(struct mock_expectation *expectation)
+{
+ return (expectation->min_calls_expected <= expectation->times_called &&
+ expectation->times_called <= expectation->max_calls_expected);
+}
+
+static void mock_write_expectation_unsatisfied_message(
+ struct mock_expectation *expectation,
+ struct test_stream *stream)
+{
+ stream->add(stream,
+ "Expectation was not called the specified number of times:\n\t");
+ stream->add(stream,
+ "Function: %s, min calls: %d, max calls: %d, actual calls: %d",
+ expectation->method->method_name,
+ expectation->min_calls_expected,
+ expectation->max_calls_expected,
+ expectation->times_called);
+}
+
void mock_validate_expectations(struct mock *mock)
{
struct mock_expectation *expectation, *expectation_safe;
struct mock_method *method;
struct test_stream *stream;
- int times_called;
stream = test_new_stream(mock->test);
list_for_each_entry(method, &mock->methods, node) {
list_for_each_entry_safe(expectation, expectation_safe,
&method->expectations, node) {
- times_called = expectation->times_called;
- if (!(expectation->min_calls_expected <= times_called &&
- times_called <= expectation->max_calls_expected)
- ) {
- stream->add(stream,
- "Expectation was not called the specified number of times:\n\t");
- stream->add(stream,
- "Function: %s, min calls: %d, max calls: %d, actual calls: %d",
- expectation->method->method_name,
- expectation->min_calls_expected,
- expectation->max_calls_expected,
- times_called);
+ if (!mock_is_expectation_satisfied(expectation)) {
+ mock_write_expectation_unsatisfied_message(
+ expectation, stream);
mock->test->fail(mock->test, stream);
}
list_del(&expectation->node);
@@ -203,6 +213,7 @@
expectation->max_calls_expected = 1;
expectation->min_calls_expected = 1;
+ INIT_LIST_HEAD(&expectation->prerequisites);
ret = mock_add_expectation(mock, method_name, method_ptr, expectation);
if (ret < 0)
return NULL;
@@ -341,6 +352,61 @@
method->method_name, type_names, params, len);
}
+static bool mock_are_prereqs_satisfied(struct mock_expectation *expectation,
+ struct test_stream *stream)
+{
+ struct mock_expectation_prereq_entry *entry, *entry_safe;
+
+ list_for_each_entry_safe(entry, entry_safe,
+ &expectation->prerequisites, node) {
+ if (!mock_is_expectation_satisfied(entry->expectation)) {
+ stream->add(stream,
+ "Expectation %s matched but prerequisite expectation was not satisfied:\n",
+ expectation->expectation_name);
+ mock_write_expectation_unsatisfied_message(
+ entry->expectation, stream);
+ return false;
+ }
+ /* Don't need to check satisfied prereq again. */
+ list_del(&entry->node);
+ }
+ return true;
+}
+
+/* Assumes that the var args are null terminated. */
+int mock_in_sequence(struct test *test, struct mock_expectation *first, ...)
+{
+ struct mock_expectation *prereq = first;
+ struct mock_expectation *curr = NULL;
+ struct mock_expectation_prereq_entry *entry;
+ va_list args;
+
+ va_start(args, first);
+
+ while ((curr = va_arg(args, struct mock_expectation*))) {
+ entry = test_kzalloc(test, sizeof(*entry), GFP_KERNEL);
+ if (!entry) {
+ va_end(args);
+ return -ENOMEM;
+ }
+ entry->expectation = prereq;
+ list_add_tail(&entry->node, &curr->prerequisites);
+ prereq = curr;
+ }
+ va_end(args);
+ return 0;
+}
+
+static inline bool does_mock_expectation_match_call(
+ struct mock_expectation *expectation,
+ struct test_stream *stream,
+ const void **params,
+ int len)
+{
+ return mock_match_params(expectation->matcher, stream, params, len) &&
+ mock_are_prereqs_satisfied(expectation, stream);
+}
+
static struct mock_expectation *mock_apply_expectations(
struct mock *mock,
struct mock_method *method,
@@ -382,7 +448,7 @@
attempted_matching_stream->add(attempted_matching_stream,
"Tried expectation: %s, but\n", ret->expectation_name);
- if (mock_match_params(ret->matcher,
+ if (does_mock_expectation_match_call(ret,
attempted_matching_stream, params, len)) {
/*
* Matcher was found; we won't print, so clean up the