Skip to content

Commit d39a11e

Browse files
author
Martin Krulis
committed
A tool for low level DB operations. Functions that check exercise and assignment config-files integrity implemented.
1 parent d64cf72 commit d39a11e

10 files changed

+381
-0
lines changed

.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,5 @@ installation/virtual_*
1010
*.pyc
1111

1212
/regression_tests/.vscode
13+
/lowlevel_db_ops/config/config.ini.php
14+
/lowlevel_db_ops/vendor
+64
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
<?php
2+
3+
require_once(__DIR__ . '/BaseCommand.php');
4+
require_once(__DIR__ . '/helpers.php');
5+
6+
7+
class Assignments extends BaseCommand
8+
{
9+
10+
11+
private function verifyAssignmentFiles($assignment)
12+
{
13+
$config = $this->getExerciseConfig($assignment->exercise_config_id);
14+
$variables = harvestConfigVariables($config);
15+
$varFiles = harvestRemoteFilesFromVariables($variables);
16+
$supFiles = $this->getAssignmentSupplementaryFiles($assignment->id);
17+
$missingFiles = [];
18+
foreach ($varFiles as $file) {
19+
if (empty($supFiles[$file])) $missingFiles[] = $file;
20+
}
21+
return $missingFiles;
22+
}
23+
24+
25+
/*
26+
* Public interface
27+
*/
28+
29+
public function verifyFilesIntegrity()
30+
{
31+
$assignments = $this->getAssignments();
32+
echo "Checking ", $assignments->getRowCount(), " assignments..";
33+
$failed = [];
34+
$missingFiles = [];
35+
foreach ($assignments as $assignment) {
36+
$res = $this->verifyAssignmentFiles($assignment);
37+
if ($res) {
38+
$failed[] = $assignment;
39+
$missingFiles[$assignment->id] = $res;
40+
echo 'X';
41+
}
42+
else
43+
echo '.';
44+
}
45+
echo $failed ? (" " . count($failed) . " failed") : "OK";
46+
echo "\n\n";
47+
if (!$failed) return;
48+
49+
$relevant = 0;
50+
foreach ($failed as $assignment) {
51+
$deadline = $assignment->allow_second_deadline ? $assignment->second_deadline : $assignment->first_deadline;
52+
if ($deadline->diff(new DateTime())->days > 68) continue; // too old
53+
54+
++$relevant;
55+
echo "Integrity failed in assignment ", $assignment->id,
56+
" of exercise ", $assignment->exercise_id, ": ", $this->getAssignmentName($assignment->id), "\n";
57+
echo "Last deadline: ", $deadline->format('j.n.Y H:i'), "\n";
58+
echo "In group ", $assignment->group_id, ": ", $this->getGroupName($assignment->group_id), "\n";
59+
echo "Files missing: ", join(", ", $missingFiles[$assignment->id]), "\n";
60+
echo "-----------------------\n\n";
61+
}
62+
echo "$relevant relevant failures reported\n";
63+
}
64+
}
+78
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
<?php
2+
3+
4+
class BaseCommand
5+
{
6+
protected $db;
7+
8+
public function __construct($db)
9+
{
10+
$this->db = $db;
11+
}
12+
13+
protected function getExerciseName($exerciseId, $locale = 'en')
14+
{
15+
return getLocaleSafe( $this->db->fetchPairs('SELECT le.locale, le.name FROM exercise_localized_exercise AS ele
16+
JOIN localized_exercise AS le ON le.id = ele.localized_exercise_id
17+
WHERE ele.exercise_id = ?', $exerciseId),
18+
$locale
19+
);
20+
}
21+
22+
23+
protected function getAssignmentName($assignmentId, $locale = 'en')
24+
{
25+
return getLocaleSafe( $this->db->fetchPairs('SELECT le.locale, le.name FROM assignment_localized_exercise AS ale
26+
JOIN localized_exercise AS le ON le.id = ale.localized_exercise_id
27+
WHERE ale.assignment_id = ?', $assignmentId),
28+
$locale
29+
);
30+
}
31+
32+
33+
protected function getGroupName($groupId, $locale = 'en')
34+
{
35+
return getLocaleSafe(
36+
$this->db->fetchPairs('SELECT locale, name FROM localized_group WHERE group_id = ?', $groupId),
37+
$locale);
38+
}
39+
40+
protected function getUserName($userId)
41+
{
42+
return $this->db->fetchSingle("SELECT CONCAT(first_name, ' ', last_name) FROM user WHERE id = ?", $userId);
43+
}
44+
45+
46+
protected function getExercises()
47+
{
48+
return $this->db->query("SELECT * FROM exercise WHERE deleted_at IS NULL");
49+
}
50+
51+
52+
protected function getAssignments()
53+
{
54+
return $this->db->query("SELECT * FROM assignment WHERE deleted_at IS NULL");
55+
}
56+
57+
58+
protected function getExerciseConfig($configId)
59+
{
60+
$configYaml = $this->db->fetchSingle('SELECT config FROM exercise_config WHERE id = ?', $configId);
61+
return yaml_parse($configYaml);
62+
}
63+
64+
65+
protected function getAssignmentSupplementaryFiles($assignmentId)
66+
{
67+
return $this->db->fetchPairs('SELECT f.name, f.id FROM assignment_supplementary_exercise_file AS af
68+
JOIN uploaded_file AS f ON f.id = af.supplementary_exercise_file_id
69+
WHERE af.assignment_id = ?', $assignmentId);
70+
}
71+
72+
protected function getExerciseSupplementaryFiles($exerciseId)
73+
{
74+
return $this->db->fetchPairs('SELECT f.name, f.id FROM exercise_supplementary_exercise_file AS ef
75+
JOIN uploaded_file AS f ON f.id = ef.supplementary_exercise_file_id
76+
WHERE ef.exercise_id = ?', $exerciseId);
77+
}
78+
}
+54
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
<?php
2+
3+
require_once(__DIR__ . '/BaseCommand.php');
4+
require_once(__DIR__ . '/helpers.php');
5+
6+
7+
class Exercises extends BaseCommand
8+
{
9+
10+
11+
private function verifyExerciseFiles($exercise)
12+
{
13+
$config = $this->getExerciseConfig($exercise->exercise_config_id);
14+
$variables = harvestConfigVariables($config);
15+
$varFiles = harvestRemoteFilesFromVariables($variables);
16+
$supFiles = $this->getExerciseSupplementaryFiles($exercise->id);
17+
$missingFiles = [];
18+
foreach ($varFiles as $file) {
19+
if (empty($supFiles[$file])) $missingFiles[] = $file;
20+
}
21+
return $missingFiles;
22+
}
23+
24+
25+
/*
26+
* Public interface
27+
*/
28+
29+
public function verifyFilesIntegrity()
30+
{
31+
$exercises = $this->getExercises();
32+
echo "Checking ", $exercises->getRowCount(), " exercises..";
33+
$failed = [];
34+
$missingFiles = [];
35+
foreach ($exercises as $exercise) {
36+
$res = $this->verifyExerciseFiles($exercise);
37+
if ($res) {
38+
$failed[] = $exercise;
39+
$missingFiles[$exercise->id] = $res;
40+
echo 'X';
41+
}
42+
else
43+
echo '.';
44+
}
45+
echo "\n\n";
46+
47+
foreach ($failed as $exercise) {
48+
echo "Integrity failed in exercise ", $exercise->id, ": ", $this->getExerciseName($exercise->id), "\n";
49+
echo "Author:", $this->getUserName($exercise->author_id), "\n";
50+
echo "Files missing: ", join(", ", $missingFiles[$exercise->id]), "\n";
51+
echo "-----------------------\n\n";
52+
}
53+
}
54+
}

lowlevel_db_ops/commands/helpers.php

+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
<?php
2+
3+
function getLocaleSafe(array $localizedText, $locale = 'en')
4+
{
5+
if (!$localizedText) return '??';
6+
return empty($localizedText[$locale]) ? reset($localizedText) : $localizedText[$locale];
7+
}
8+
9+
function harvestConfigVariables($config)
10+
{
11+
$res = [];
12+
// Let's swine up some PHP...
13+
if (empty($config['tests']) || !is_array($config['tests'])) return [];
14+
foreach ($config['tests'] as $test) {
15+
if (empty($test['environments']) || !is_array($test['environments'])) continue;
16+
foreach ($test['environments'] as $environment) {
17+
if (empty($environment['pipelines']) || !is_array($environment['pipelines'])) continue;
18+
foreach ($environment['pipelines'] as $pipeline) {
19+
if (!empty($pipeline['variables']) && is_array($pipeline['variables'])) {
20+
$res = array_merge($res, $pipeline['variables']);
21+
}
22+
}
23+
}
24+
}
25+
return $res;
26+
}
27+
28+
29+
function harvestRemoteFilesFromVariables($variables)
30+
{
31+
$files = [];
32+
foreach ($variables as $variable) {
33+
$value = $variable['value'];
34+
if (!$value) continue;
35+
36+
if ($variable['type'] === 'remote-file') {
37+
$files[$value] = true;
38+
} elseif ($variable['type'] === 'remote-file[]') {
39+
foreach ($value as $v) $files[$v] = true;
40+
}
41+
}
42+
return array_keys($files);
43+
}
44+

lowlevel_db_ops/composer.json

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"require": {
3+
"dibi/dibi": "^4.0"
4+
}
5+
}

lowlevel_db_ops/composer.lock

+82
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<?php
2+
3+
return [
4+
'driver' => 'mysqli',
5+
'host' => 'localhost',
6+
'username' => 'recodex-api',
7+
'password' => 'recodex',
8+
'database' => 'recodex-api',
9+
];

lowlevel_db_ops/index.php

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
<?php
2+
3+
require_once('vendor/autoload.php');
4+
5+
6+
function recodex_lowlevel_db_ops_main($argv) {
7+
foreach (glob('commands/*.php') as $file)
8+
require_once($file);
9+
10+
11+
array_shift($argv); // skip script name
12+
13+
if (empty($argv[0])) {
14+
echo "No command provided.\n";
15+
die;
16+
}
17+
18+
list($class, $method) = explode(':', array_shift($argv));
19+
if (!class_exists($class))
20+
throw new Exception("Class $class not found.");
21+
22+
// Init DB...
23+
$config = require('config/config.ini.php');
24+
if (!$config)
25+
throw new Exception("No config found");
26+
$database = new Dibi\Connection($config);
27+
28+
$command = new $class($database);
29+
if (!method_exists($command, $method))
30+
throw new Exception("Method $method does not exists in class $class.");
31+
32+
$command->$method(...$argv);
33+
}
34+
35+
36+
try {
37+
recodex_lowlevel_db_ops_main($argv);
38+
}
39+
catch (Exception $e) {
40+
echo "Error: ", $e->getMessage(), "\n";
41+
exit(1);
42+
}

regression_tests/exercises.list

+1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# Compiler principles (1-3)
22
7212206f-ccd8-11e8-a4be-00505601122b
33
ac2d1262-d779-11e8-a4be-00505601122b
4+
# Tenhle test ma nedeterministicky zobrazovany log (Test Array 25)
45
e0daf3be-d869-11e8-a4be-00505601122b
56

67
# Web apps

0 commit comments

Comments
 (0)