Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 4.1.x will end 13 November 2023 (12 months).
  • Bug fixes for security issues in 4.1.x will end 10 November 2025 (36 months).
  • PHP version: minimum PHP 7.4.0 Note: minimum PHP version has increased since Moodle 4.0. PHP 8.0.x is supported too.

Differences Between: [Versions 310 and 401] [Versions 39 and 401]

   1  <?php
   2  // This file is part of Moodle - http://moodle.org/
   3  //
   4  // Moodle is free software: you can redistribute it and/or modify
   5  // it under the terms of the GNU General Public License as published by
   6  // the Free Software Foundation, either version 3 of the License, or
   7  // (at your option) any later version.
   8  //
   9  // Moodle is distributed in the hope that it will be useful,
  10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  12  // GNU General Public License for more details.
  13  //
  14  // You should have received a copy of the GNU General Public License
  15  // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
  16  
  17  /**
  18   * Unit tests for Random allocation
  19   *
  20   * @package    workshopallocation_random
  21   * @category   test
  22   * @copyright  2009 David Mudrak <david.mudrak@gmail.com>
  23   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  24   */
  25  
  26  namespace workshopallocation_random;
  27  
  28  use workshop;
  29  use workshop_random_allocator;
  30  
  31  defined('MOODLE_INTERNAL') || die();
  32  
  33  // Include the code to test
  34  global $CFG;
  35  require_once($CFG->dirroot . '/mod/workshop/locallib.php');
  36  require_once($CFG->dirroot . '/mod/workshop/allocation/random/lib.php');
  37  
  38  /**
  39   * Unit tests for Random allocation
  40   *
  41   * @package    workshopallocation_random
  42   * @category   test
  43   * @copyright  2009 David Mudrak <david.mudrak@gmail.com>
  44   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  45   */
  46  class allocator_test extends \advanced_testcase {
  47  
  48      /** workshop instance emulation */
  49      protected $workshop;
  50  
  51      /** allocator instance */
  52      protected $allocator;
  53  
  54      protected function setUp(): void {
  55          parent::setUp();
  56          $this->resetAfterTest();
  57          $this->setAdminUser();
  58          $course = $this->getDataGenerator()->create_course();
  59          $workshop = $this->getDataGenerator()->create_module('workshop', array('course' => $course));
  60          $cm = get_fast_modinfo($course)->instances['workshop'][$workshop->id];
  61          $this->workshop = new workshop($workshop, $cm, $course);
  62          $this->allocator = new testable_workshop_random_allocator($this->workshop);
  63      }
  64  
  65      protected function tearDown(): void {
  66          $this->allocator    = null;
  67          $this->workshop     = null;
  68          parent::tearDown();
  69      }
  70  
  71      public function test_self_allocation_empty_values() {
  72          // fixture setup & exercise SUT & verify
  73          $this->assertEquals(array(), $this->allocator->self_allocation());
  74      }
  75  
  76      public function test_self_allocation_equal_user_groups() {
  77          // fixture setup
  78          $authors    = array(0 => array_fill_keys(array(4, 6, 10), new \stdClass()));
  79          $reviewers  = array(0 => array_fill_keys(array(4, 6, 10), new \stdClass()));
  80          // exercise SUT
  81          $newallocations = $this->allocator->self_allocation($authors, $reviewers);
  82          // verify
  83          $this->assertEquals(array(array(4 => 4), array(6 => 6), array(10 => 10)), $newallocations);
  84      }
  85  
  86      public function test_self_allocation_different_user_groups() {
  87          // fixture setup
  88          $authors    = array(0 => array_fill_keys(array(1, 4, 5, 10, 13), new \stdClass()));
  89          $reviewers  = array(0 => array_fill_keys(array(4, 7, 10), new \stdClass()));
  90          // exercise SUT
  91          $newallocations = $this->allocator->self_allocation($authors, $reviewers);
  92          // verify
  93          $this->assertEquals(array(array(4 => 4), array(10 => 10)), $newallocations);
  94      }
  95  
  96      public function test_self_allocation_skip_existing() {
  97          // fixture setup
  98          $authors        = array(0 => array_fill_keys(array(3, 4, 10), new \stdClass()));
  99          $reviewers      = array(0 => array_fill_keys(array(3, 4, 10), new \stdClass()));
 100          $assessments    = array(23 => (object)array('authorid' => 3, 'reviewerid' => 3));
 101          // exercise SUT
 102          $newallocations = $this->allocator->self_allocation($authors, $reviewers, $assessments);
 103          // verify
 104          $this->assertEquals(array(array(4 => 4), array(10 => 10)), $newallocations);
 105      }
 106  
 107      public function test_get_author_ids() {
 108          // fixture setup
 109          $newallocations = array(array(1 => 3), array(2 => 1), array(3 => 1));
 110          // exercise SUT & verify
 111          $this->assertEquals(array(3, 1), $this->allocator->get_author_ids($newallocations));
 112      }
 113  
 114      public function test_index_submissions_by_authors() {
 115          // fixture setup
 116          $submissions = array(
 117              676 => (object)array('id' => 676, 'authorid' => 23),
 118              121 => (object)array('id' => 121, 'authorid' => 56),
 119          );
 120          // exercise SUT
 121          $submissions = $this->allocator->index_submissions_by_authors($submissions);
 122          // verify
 123          $this->assertEquals(array(
 124              23 => (object)array('id' => 676, 'authorid' => 23),
 125              56 => (object)array('id' => 121, 'authorid' => 56),
 126          ), $submissions);
 127      }
 128  
 129      public function test_index_submissions_by_authors_duplicate_author() {
 130          // fixture setup
 131          $submissions = array(
 132              14 => (object)array('id' => 676, 'authorid' => 3),
 133              87 => (object)array('id' => 121, 'authorid' => 3),
 134          );
 135          // exercise SUT
 136          $this->expectException(\moodle_exception::class);
 137          $submissions = $this->allocator->index_submissions_by_authors($submissions);
 138      }
 139  
 140      public function test_get_unique_allocations() {
 141          // fixture setup
 142          $newallocations = array(array(4 => 5), array(6 => 6), array(1 => 16), array(4 => 5), array(16 => 1));
 143          // exercise SUT
 144          $newallocations = $this->allocator->get_unique_allocations($newallocations);
 145          // verify
 146          $this->assertEquals(array(array(4 => 5), array(6 => 6), array(1 => 16), array(16 => 1)), $newallocations);
 147      }
 148  
 149      public function test_get_unkept_assessments_no_keep_selfassessments() {
 150          // fixture setup
 151          $assessments = array(
 152              23 => (object)array('authorid' => 3, 'reviewerid' => 3),
 153              45 => (object)array('authorid' => 5, 'reviewerid' => 11),
 154              12 => (object)array('authorid' => 6, 'reviewerid' => 3),
 155          );
 156          $newallocations = array(array(4 => 5), array(11 => 5), array(1 => 16), array(4 => 5), array(16 => 1));
 157          // exercise SUT
 158          $delassessments = $this->allocator->get_unkept_assessments($assessments, $newallocations, false);
 159          // verify
 160          // we want to keep $assessments[45] because it has been re-allocated
 161          $this->assertEquals(array(23, 12), $delassessments);
 162      }
 163  
 164      public function test_get_unkept_assessments_keep_selfassessments() {
 165          // fixture setup
 166          $assessments = array(
 167              23 => (object)array('authorid' => 3, 'reviewerid' => 3),
 168              45 => (object)array('authorid' => 5, 'reviewerid' => 11),
 169              12 => (object)array('authorid' => 6, 'reviewerid' => 3),
 170          );
 171          $newallocations = array(array(4 => 5), array(11 => 5), array(1 => 16), array(4 => 5), array(16 => 1));
 172          // exercise SUT
 173          $delassessments = $this->allocator->get_unkept_assessments($assessments, $newallocations, true);
 174          // verify
 175          // we want to keep $assessments[45] because it has been re-allocated
 176          // we want to keep $assessments[23] because if is self assessment
 177          $this->assertEquals(array(12), $delassessments);
 178      }
 179  
 180      /**
 181       * Aggregates assessment info per author and per reviewer
 182       */
 183      public function test_convert_assessments_to_links() {
 184          // fixture setup
 185          $assessments = array(
 186              23 => (object)array('authorid' => 3, 'reviewerid' => 3),
 187              45 => (object)array('authorid' => 5, 'reviewerid' => 11),
 188              12 => (object)array('authorid' => 5, 'reviewerid' => 3),
 189          );
 190          // exercise SUT
 191          list($authorlinks, $reviewerlinks) = $this->allocator->convert_assessments_to_links($assessments);
 192          // verify
 193          $this->assertEquals(array(3 => array(3), 5 => array(11, 3)), $authorlinks);
 194          $this->assertEquals(array(3 => array(3, 5), 11 => array(5)), $reviewerlinks);
 195      }
 196  
 197      /**
 198       * Trivial case
 199       */
 200      public function test_convert_assessments_to_links_empty() {
 201          // fixture setup
 202          $assessments = array();
 203          // exercise SUT
 204          list($authorlinks, $reviewerlinks) = $this->allocator->convert_assessments_to_links($assessments);
 205          // verify
 206          $this->assertEquals(array(), $authorlinks);
 207          $this->assertEquals(array(), $reviewerlinks);
 208      }
 209  
 210      /**
 211       * If there is a single element with the lowest workload, it should be chosen
 212       */
 213      public function test_get_element_with_lowest_workload_deterministic() {
 214          // fixture setup
 215          $workload = array(4 => 6, 9 => 1, 10 => 2);
 216          // exercise SUT
 217          $chosen = $this->allocator->get_element_with_lowest_workload($workload);
 218          // verify
 219          $this->assertEquals(9, $chosen);
 220      }
 221  
 222      /**
 223       * If there are no elements available, must return false
 224       */
 225      public function test_get_element_with_lowest_workload_impossible() {
 226          // fixture setup
 227          $workload = array();
 228          // exercise SUT
 229          $chosen = $this->allocator->get_element_with_lowest_workload($workload);
 230          // verify
 231          $this->assertTrue($chosen === false);
 232      }
 233  
 234      /**
 235       * If there are several elements with the lowest workload, one of them should be chosen randomly
 236       */
 237      public function test_get_element_with_lowest_workload_random() {
 238          // fixture setup
 239          $workload = array(4 => 6, 9 => 2, 10 => 2);
 240          // exercise SUT
 241          $elements = $this->allocator->get_element_with_lowest_workload($workload);
 242          // verify
 243          // in theory, this test can fail even if the function works well. However, the probability of getting
 244          // a row of a hundred same ids in this use case is 1/pow(2, 100)
 245          // also, this just tests that each of the two elements has been chosen at least once. this is not to
 246          // measure the quality or randomness of the algorithm
 247          $counts = array(4 => 0, 9 => 0, 10 => 0);
 248          for ($i = 0; $i < 100; $i++) {
 249              $chosen = $this->allocator->get_element_with_lowest_workload($workload);
 250              if (!in_array($chosen, array(4, 9, 10))) {
 251                  $this->fail('Invalid element ' . var_export($chosen, true) . ' chosen');
 252                  break;
 253              } else {
 254                  $counts[$this->allocator->get_element_with_lowest_workload($workload)]++;
 255              }
 256          }
 257          $this->assertTrue(($counts[9] > 0) && ($counts[10] > 0));
 258      }
 259  
 260      /**
 261       * Floats should be rounded before they are compared
 262       *
 263       * This should test
 264       */
 265      public function test_get_element_with_lowest_workload_random_floats() {
 266          // fixture setup
 267          $workload = array(1 => 1/13, 2 => 0.0769230769231); // should be considered as the same value
 268          // exercise SUT
 269          $elements = $this->allocator->get_element_with_lowest_workload($workload);
 270          // verify
 271          $counts = array(1 => 0, 2 => 0);
 272          for ($i = 0; $i < 100; $i++) {
 273              $chosen = $this->allocator->get_element_with_lowest_workload($workload);
 274              if (!in_array($chosen, array(1, 2))) {
 275                  $this->fail('Invalid element ' . var_export($chosen, true) . ' chosen');
 276                  break;
 277              } else {
 278                  $counts[$this->allocator->get_element_with_lowest_workload($workload)]++;
 279              }
 280          }
 281          $this->assertTrue(($counts[1] > 0) && ($counts[2] > 0));
 282  
 283      }
 284  
 285      /**
 286       * Filter new assessments so they do not contain existing
 287       */
 288      public function test_filter_current_assessments() {
 289          // fixture setup
 290          $newallocations = array(array(3 => 5), array(11 => 5), array(2 => 9), array(3 => 5));
 291          $assessments = array(
 292              23 => (object)array('authorid' => 3, 'reviewerid' => 3),
 293              45 => (object)array('authorid' => 5, 'reviewerid' => 11),
 294              12 => (object)array('authorid' => 5, 'reviewerid' => 3),
 295          );
 296          // exercise SUT
 297          $this->allocator->filter_current_assessments($newallocations, $assessments);
 298          // verify
 299          $this->assertEquals(array_values($newallocations), array(array(2 => 9)));
 300      }
 301  
 302  
 303  }
 304  
 305  
 306  /**
 307   * Make protected methods we want to test public
 308   */
 309  class testable_workshop_random_allocator extends workshop_random_allocator {
 310      public function self_allocation($authors=array(), $reviewers=array(), $assessments=array()) {
 311          return parent::self_allocation($authors, $reviewers, $assessments);
 312      }
 313      public function get_author_ids($newallocations) {
 314          return parent::get_author_ids($newallocations);
 315      }
 316      public function index_submissions_by_authors($submissions) {
 317          return parent::index_submissions_by_authors($submissions);
 318      }
 319      public function get_unique_allocations($newallocations) {
 320          return parent::get_unique_allocations($newallocations);
 321      }
 322      public function get_unkept_assessments($assessments, $newallocations, $keepselfassessments) {
 323          return parent::get_unkept_assessments($assessments, $newallocations, $keepselfassessments);
 324      }
 325      public function convert_assessments_to_links($assessments) {
 326          return parent::convert_assessments_to_links($assessments);
 327      }
 328      public function get_element_with_lowest_workload($workload) {
 329          return parent::get_element_with_lowest_workload($workload);
 330      }
 331      public function filter_current_assessments(&$newallocations, $assessments) {
 332          return parent::filter_current_assessments($newallocations, $assessments);
 333      }
 334  }