From 165e26fad953116eb59e9c887e1f0836021a2863 Mon Sep 17 00:00:00 2001 From: Paul Holden Date: Fri, 11 Mar 2022 10:53:35 +0000 Subject: [PATCH] MDL-73938 reportbuilder: helper methods for automating report tests. Implement stress tester methods for iterating over report columns, aggregation and conditions. Assert that each works correctly in isolation, and when used in conjunction with other columns. --- .../datasource/task_logs_test.php | 13 ++ .../reportbuilder/datasource/badges_test.php | 19 +++ .../datasource/cohorts_test.php} | 17 ++- .../reportbuilder/datasource/courses_test.php | 20 +++ .../datasource/participants_test.php | 14 +++ reportbuilder/tests/helpers.php | 119 ++++++++++++++++++ reportbuilder/upgrade.txt | 4 + .../reportbuilder/datasource/tags_test.php | 13 ++ .../reportbuilder/datasource/users_test.php | 16 ++- 9 files changed, 233 insertions(+), 2 deletions(-) rename cohort/tests/{datasource_test.php => reportbuilder/datasource/cohorts_test.php} (89%) diff --git a/admin/tests/reportbuilder/datasource/task_logs_test.php b/admin/tests/reportbuilder/datasource/task_logs_test.php index 0c77fea4c34..0c5e20f8336 100644 --- a/admin/tests/reportbuilder/datasource/task_logs_test.php +++ b/admin/tests/reportbuilder/datasource/task_logs_test.php @@ -221,6 +221,19 @@ class task_logs_test extends core_reportbuilder_testcase { } } + /** + * Stress test datasource + */ + public function test_stress_datasource(): void { + $this->resetAfterTest(); + + $this->generate_task_log_data(true, 3, 2, 1654038000, 1654038060, 'hi', 'core_reportbuilder', 'test', 43); + + $this->datasource_stress_test_columns(task_logs::class); + $this->datasource_stress_test_columns_aggregation(task_logs::class); + $this->datasource_stress_test_conditions(task_logs::class, 'task_log:name'); + } + /** * Helper to generate some task logs data * diff --git a/badges/tests/reportbuilder/datasource/badges_test.php b/badges/tests/reportbuilder/datasource/badges_test.php index 7687a8ca7cf..1353015c47d 100644 --- a/badges/tests/reportbuilder/datasource/badges_test.php +++ b/badges/tests/reportbuilder/datasource/badges_test.php @@ -94,4 +94,23 @@ class badges_test extends core_reportbuilder_testcase { return array_values($row); }, $content)); } + + /** + * Stress test datasource + */ + public function test_stress_datasource(): void { + $this->resetAfterTest(); + + $course = $this->getDataGenerator()->create_course(); + $user = $this->getDataGenerator()->create_and_enrol($course); + + /** @var core_badges_generator $generator */ + $generator = $this->getDataGenerator()->get_plugin_generator('core_badges'); + $badge = $generator->create_badge(['name' => 'Course badge', 'type' => BADGE_TYPE_COURSE, 'courseid' => $course->id]); + $badge->issue($user->id, true); + + $this->datasource_stress_test_columns(badges::class); + $this->datasource_stress_test_columns_aggregation(badges::class); + $this->datasource_stress_test_conditions(badges::class, 'badge:name'); + } } diff --git a/cohort/tests/datasource_test.php b/cohort/tests/reportbuilder/datasource/cohorts_test.php similarity index 89% rename from cohort/tests/datasource_test.php rename to cohort/tests/reportbuilder/datasource/cohorts_test.php index 0d47358b27f..777d874f696 100644 --- a/cohort/tests/datasource_test.php +++ b/cohort/tests/reportbuilder/datasource/cohorts_test.php @@ -37,7 +37,7 @@ require_once("{$CFG->dirroot}/reportbuilder/tests/helpers.php"); * @copyright 2021 Paul Holden * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -class datasource_test extends core_reportbuilder_testcase { +class cohorts_test extends core_reportbuilder_testcase { /** * Test cohorts datasource @@ -132,4 +132,19 @@ class datasource_test extends core_reportbuilder_testcase { $contentrow = array_values(reset($content)); $this->assertEquals([$expectedcohort, $username], $contentrow); } + + /** + * Stress test datasource + */ + public function test_stress_datasource(): void { + $this->resetAfterTest(); + + $cohort = $this->getDataGenerator()->create_cohort(); + $user = $this->getDataGenerator()->create_user(); + cohort_add_member($cohort->id, $user->id); + + $this->datasource_stress_test_columns(cohorts::class); + $this->datasource_stress_test_columns_aggregation(cohorts::class); + $this->datasource_stress_test_conditions(cohorts::class, 'cohort:name'); + } } diff --git a/course/tests/reportbuilder/datasource/courses_test.php b/course/tests/reportbuilder/datasource/courses_test.php index 77c728f5fc4..7cae62a4807 100644 --- a/course/tests/reportbuilder/datasource/courses_test.php +++ b/course/tests/reportbuilder/datasource/courses_test.php @@ -18,6 +18,7 @@ declare(strict_types=1); namespace core_course\reportbuilder\datasource; +use core_customfield_generator; use core_reportbuilder_testcase; use core_reportbuilder_generator; use core_reportbuilder\local\filters\tags; @@ -192,4 +193,23 @@ class courses_test extends core_reportbuilder_testcase { $this->assertEmpty($content); } } + + /** + * Stress test datasource + */ + public function test_stress_datasource(): void { + $this->resetAfterTest(); + + /** @var core_customfield_generator $generator */ + $generator = $this->getDataGenerator()->get_plugin_generator('core_customfield'); + $customfieldcategory = $generator->create_category(); + $generator->create_field(['categoryid' => $customfieldcategory->get('id'), 'shortname' => 'hi']); + + $category = $this->getDataGenerator()->create_category(); + $course = $this->getDataGenerator()->create_course(['category' => $category->id, 'customfield_hi' => 'Hello']); + + $this->datasource_stress_test_columns(courses::class); + $this->datasource_stress_test_columns_aggregation(courses::class); + $this->datasource_stress_test_conditions(courses::class, 'course:idnumber'); + } } diff --git a/course/tests/reportbuilder/datasource/participants_test.php b/course/tests/reportbuilder/datasource/participants_test.php index 5bc5bc3e545..e059d376493 100644 --- a/course/tests/reportbuilder/datasource/participants_test.php +++ b/course/tests/reportbuilder/datasource/participants_test.php @@ -277,4 +277,18 @@ class participants_test extends core_reportbuilder_testcase { $this->assertCount(1, $content); $this->assertEquals($expected, $content[0]['c0_firstname']); } + + /** + * Stress test datasource + */ + public function test_stress_datasource(): void { + $this->resetAfterTest(); + + $course = $this->getDataGenerator()->create_course(); + $this->getDataGenerator()->create_and_enrol($course); + + $this->datasource_stress_test_columns(participants::class); + $this->datasource_stress_test_columns_aggregation(participants::class); + $this->datasource_stress_test_conditions(participants::class, 'course:idnumber'); + } } diff --git a/reportbuilder/tests/helpers.php b/reportbuilder/tests/helpers.php index 38ceb04a716..3edbe3d0238 100644 --- a/reportbuilder/tests/helpers.php +++ b/reportbuilder/tests/helpers.php @@ -16,6 +16,9 @@ declare(strict_types=1); +use core_reportbuilder\manager; +use core_reportbuilder\local\helpers\aggregation; +use core_reportbuilder\local\helpers\report; use core_reportbuilder\local\helpers\user_filter_manager; use core_reportbuilder\table\custom_report_table_view; @@ -56,4 +59,120 @@ abstract class core_reportbuilder_testcase extends advanced_testcase { return $records; } + + /** + * Stress test a report source by iterating over all it's columns and asserting we can create a report for each + * + * @param string $source + */ + protected function datasource_stress_test_columns(string $source): void { + + /** @var core_reportbuilder_generator $generator */ + $generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder'); + + $report = $generator->create_report(['name' => 'Stress columns', 'source' => $source, 'default' => 0]); + $instance = manager::get_report_from_persistent($report); + + // Iterate over each available column, ensure each works correctly independent of any others. + $columnidentifiers = array_keys($instance->get_columns()); + foreach ($columnidentifiers as $columnidentifier) { + $column = report::add_report_column($report->get('id'), $columnidentifier); + + // We are only asserting the report returns content without errors, not the content itself. + try { + $content = $this->get_custom_report_content($report->get('id')); + $this->assertNotEmpty($content); + } catch (Throwable $exception) { + $this->fail("Error for column '{$columnidentifier}': " . $exception->getMessage()); + } + + report::delete_report_column($report->get('id'), $column->get('id')); + } + } + + /** + * Stress test a report source by iterating over all columns and asserting we can create a report while aggregating each + * + * @param string $source + */ + protected function datasource_stress_test_columns_aggregation(string $source): void { + + /** @var core_reportbuilder_generator $generator */ + $generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder'); + + $report = $generator->create_report(['name' => 'Stress aggregation', 'source' => $source, 'default' => 0]); + $instance = manager::get_report_from_persistent($report); + + // Add every column. + $columnidentifiers = array_keys($instance->get_columns()); + foreach ($columnidentifiers as $columnidentifier) { + report::add_report_column($report->get('id'), $columnidentifier); + } + + // Now iterate over each column, and apply all suitable aggregation types. + foreach ($instance->get_active_columns() as $column) { + $aggregations = aggregation::get_column_aggregations($column->get_type(), $column->get_disabled_aggregation()); + foreach (array_keys($aggregations) as $aggregation) { + $column->get_persistent()->set('aggregation', $aggregation)->update(); + + // We are only asserting the report returns content without errors, not the content itself. + try { + $content = $this->get_custom_report_content($report->get('id')); + $this->assertNotEmpty($content); + } catch (Throwable $exception) { + $this->fail("Error for column '{$column->get_unique_identifier()}' with aggregation '{$aggregation}': " . + $exception->getMessage()); + } + } + + // Reset the column aggregation. + $column->get_persistent()->set('aggregation', null)->update(); + } + } + + /** + * Stress test a report source by iterating over all it's conditions and asserting we can create a report using each + * + * @param string $source + * @param string $columnidentifier Should be a simple column, with as few fields and joins as possible, ideally selected + * from the base table itself + */ + protected function datasource_stress_test_conditions(string $source, string $columnidentifier): void { + + /** @var core_reportbuilder_generator $generator */ + $generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder'); + + $report = $generator->create_report(['name' => 'Stress conditions', 'source' => $source, 'default' => 0]); + $instance = manager::get_report_from_persistent($report); + + // Add single column only (to ensure no conditions have reliance on any columns). + report::add_report_column($report->get('id'), $columnidentifier); + + // Iterate over each available condition, ensure each works correctly independent of any others. + $conditionidentifiers = array_keys($instance->get_conditions()); + foreach ($conditionidentifiers as $conditionidentifier) { + $condition = report::add_report_condition($report->get('id'), $conditionidentifier); + $conditioninstance = $instance->get_condition($condition->get('uniqueidentifier')); + + /** @var \core_reportbuilder\local\filters\base $conditionclass */ + $conditionclass = $conditioninstance->get_filter_class(); + + // Set report condition values in order to activate it. + $conditionvalues = $conditionclass::create($conditioninstance)->get_sample_values(); + if (empty($conditionvalues)) { + debugging("Missing sample values from filter '{$conditionclass}'", DEBUG_DEVELOPER); + } + $instance->set_condition_values($conditionvalues); + + // We are only asserting the report returns content without errors, not the content itself. + try { + $content = $this->get_custom_report_content($report->get('id')); + $this->assertIsArray($content); + } catch (Throwable $exception) { + $this->fail("Error for condition '{$conditionidentifier}': " . $exception->getMessage()); + } + + report::delete_report_condition($report->get('id'), $condition->get('id')); + } + } } diff --git a/reportbuilder/upgrade.txt b/reportbuilder/upgrade.txt index 25112966679..f16934e7564 100644 --- a/reportbuilder/upgrade.txt +++ b/reportbuilder/upgrade.txt @@ -40,4 +40,8 @@ Information provided here is intended especially for developers. - `tags` for reports containing entities with support for core_tag API - `autocomplete` for reports that contain pre-defined values for selection. * New method `get_sample_values()` added to base filter class, to be overridden in all filter types to support stress testing +* New test helpers for automated stress testing of report sources: + - `datasource_stress_test_columns` + - `datasource_stress_test_columns_aggregation` + - `datasource_stress_test_conditions` * The test helper method `get_custom_report_content()` now accepts a list of filter values and applies them to the report diff --git a/tag/tests/reportbuilder/datasource/tags_test.php b/tag/tests/reportbuilder/datasource/tags_test.php index 7fe2fcb81ef..5257ea9e26f 100644 --- a/tag/tests/reportbuilder/datasource/tags_test.php +++ b/tag/tests/reportbuilder/datasource/tags_test.php @@ -258,4 +258,17 @@ class tags_test extends core_reportbuilder_testcase { $this->assertEmpty($content); } } + + /** + * Stress test datasource + */ + public function test_stress_datasource(): void { + $this->resetAfterTest(); + + $this->getDataGenerator()->create_course(['tags' => ['Horses']]); + + $this->datasource_stress_test_columns(tags::class); + $this->datasource_stress_test_columns_aggregation(tags::class); + $this->datasource_stress_test_conditions(tags::class, 'tag:name'); + } } diff --git a/user/tests/reportbuilder/datasource/users_test.php b/user/tests/reportbuilder/datasource/users_test.php index 983c77ffb71..36ab13b33ae 100644 --- a/user/tests/reportbuilder/datasource/users_test.php +++ b/user/tests/reportbuilder/datasource/users_test.php @@ -29,7 +29,7 @@ global $CFG; require_once("{$CFG->dirroot}/reportbuilder/tests/helpers.php"); /** - * Unit tests for users datasources + * Unit tests for users datasource * * @package core_user * @covers \core_user\reportbuilder\datasource\users @@ -155,4 +155,18 @@ class users_test extends core_reportbuilder_testcase { $this->assertEmpty($content); } } + + /** + * Stress test datasource + */ + public function test_stress_datasource(): void { + $this->resetAfterTest(); + + $this->getDataGenerator()->create_custom_profile_field(['datatype' => 'text', 'name' => 'Hi', 'shortname' => 'hi']); + $user = $this->getDataGenerator()->create_user(['profile_field_hi' => 'Hello']); + + $this->datasource_stress_test_columns(users::class); + $this->datasource_stress_test_columns_aggregation(users::class); + $this->datasource_stress_test_conditions(users::class, 'user:username'); + } } -- 2.43.0