PageRenderTime 29ms CodeModel.GetById 29ms RepoModel.GetById 0ms app.codeStats 0ms

/mod/workshop/allocation/random/tests/allocator_test.php

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