Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 3.9.x will end* 10 May 2021 (12 months).
  • Bug fixes for security issues in 3.9.x will end* 8 May 2023 (36 months).
  • PHP version: minimum PHP 7.2.0 Note: minimum PHP version has increased since Moodle 3.8. PHP 7.3.x and 7.4.x are supported too.

Differences Between: [Versions 39 and 310] [Versions 39 and 311] [Versions 39 and 400] [Versions 39 and 401] [Versions 39 and 402] [Versions 39 and 403]

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