Newer
Older
// monad
// For illinois.edu CS 225 spring 2011
// By Jack Toole
#include "monad.h"
namespace monad
{
void find_base_dir(const char * argv0);
void copyRequiredFiles();
void getLibs(const vector<string> & libs);
string updateFolder(const string & folder, bool link);
string getFolder(const string & folder, bool link);
void importFiles(const string & preservedFolder, const string & theSourceFolder,
const string & destFolder, const vector<string> & files);
void exec_command(const string & command);
string assignment_base;
int8_t mp_part;
const int8_t no_mp_part = -1;
string testsFolder;
string sourceFolder;
string gradeFolder;
string tempFolder;
bool clean = true;
bool update = true;
bool staff = false;
bool newtests = false;
bool provided = false;
bool verbose = false;
bool valgrind = false;
bool parallel = false;
bool help = false;
bool info = false;
bool license = false;
#if OPTIMIZE
bool optimize = true;
#else
bool optimize = false;
#endif
}
}
string kyleGetDate() {
string dateAndTime = "Current Date and Time: ";
time_t currentTime;
struct tm * timeStructure;
time(¤tTime);
timeStructure = localtime(¤tTime);
dateAndTime += asctime(timeStructure);
return dateAndTime;
}
{
using namespace monad;
output::set_error_message();
// Find monad/ directory
find_base_dir(argv[0]);
// Read in local config settings.
// Necessary to do this early for [SVN Root] url
readConfig("./", config);
processArgs(argc, argv);
cout << versioninfo::official_name << endl;
cout << "Testing " << assignment_base;
if (mp_part != no_mp_part) cout << '.' << mp_part;
cout << "..." << endl;
cout << getRandomQuote() << endl;
cout << endl;
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
cout << "Setting up test environment..." << endl;
// Read in test-specific config settings
if (mp_part == no_mp_part)
readConfig(testsFolder, config);
else
readConfig(testsFolder, config, to_string((int)mp_part));
copyRequiredFiles();
// Sleep for a second to avoid clock skew warnings
// This cummulatively adds about 5 minutes to grade each mp,
// but with the benefit of avoiding newsgroup posts
// CHANGED: Judging by previous emails, the time needed for
// this would vary significantly. not sure about solution
// sleep(1);
output::header("Compiling");
chdir(gradeFolder.c_str());
// #define MP_PART_NUMBER in proxy
ofstream mp_part_file;
mp_part_file.open("_mp_part_number.h");
if (mp_part == no_mp_part)
mp_part_file << "#define MP_PART_NUMBER NO_MP_PART" << endl;
else
mp_part_file << "#define MP_PART_NUMBER " << (int)mp_part << endl;
mp_part_file.close();
// run [Pre-Make Commands] config header
const vector<string> & processing_commands = config["Pre-Make Commands"];
for (size_t i = 0; i < processing_commands.size(); i++)
exec_command(processing_commands[i]);
string makestr = "/usr/bin/make --warn-undefined-variables -Wfatal-errors";
if (!opts::verbose)
makestr += " --quiet";
if (opts::optimize)
makestr += " OPTIMIZE=on";
if (!config["Make Options"].empty())
makestr += " " + config["Make Options"][0];
if (opts::verbose)
cout << makestr <<endl;
// Compile with make
system(makestr.c_str()); // yes, system is insecure if the user has control
// over config.ini. But students don't.
// TODO (toole1): Yeah but this leaves us open to
// aliasing issues, or forces us to specify make's
// path. Ugly either way.
// TODO exec("make", "--quiet", "--warn-undefined-variables",
cout << endl << endl;
const char * verboseflag = (opts::verbose ? "--verbose" : NULL);
const char * valgrindflag = (opts::valgrind ? "--valgrind" : NULL);
int score = exec("./proxy", verboseflag, valgrindflag);
// TODO (toole1): this causes weird output when scores are like 200
if (score < 0)
{
output::header("Running tests");
cout << "Could not execute test cases" << endl << endl;
score = 0;
cout << endl;
void monad::processArgs(int argc, const char * const * argv)
{
// Create OptionsMap for options and vector for positional arguments:
options.addOption("solution", opts::solution);
options.addOption("newtests", opts::newtests);
options.addOption("provided", opts::provided);
options.addOption("clean", opts::clean);
options.addOption("update", opts::update);
options.addOption("staff", opts::staff);
options.addOption("optimize", opts::optimize);
options.addOption("verbose", opts::verbose);
options.addOption("valgrind", opts::valgrind);
options.addOption("parallel", opts::parallel);
options.addOption("help", opts::help);
options.addOption("h", opts::help);
options.addOption("info", opts::info);
options.addOption("version", opts::info);
options.addOption("v", opts::info);
options.addOption("license", opts::license);
// Add arguments
string assignment = "";
options.addArg(assignment);
if (toLower(assignment) == "config")
printHelpConfig();
else if (toLower(assignment) == "tests")
printHelpTests();
else
printHelp();
// License
if (opts::license)
{
printLicense();
exit(0);
}
// Clean
if (toLower(assignment) == "clean")
{
system("/bin/rm -rf *_grade/ *_tests/ *_newtests/ *_providedtests *_solution/");
exit(0);
}
{
cout << "Usage: " << argv[0] << " mp1" << endl;
cout << "Run \'" << argv[0] << " --help\' for more information" << endl;
exit(0);
}
// Find mp/lab name and tests folder
vector<string> splitname = tokenize(assignment, '.');
assignment_base = splitname[0];
if (!exists(gradeFolder)) opts::clean = true;
if (opts::clean)
tempFolder = "";
else
if (opts::solution)
sourceFolder = updateFolder(assignment_base + "_solution/", false);
testsFolder = updateFolder(assignment_base + "_provided/", false);
testsFolder = updateFolder(assignment_base + "_newtests/", false);
testsFolder = updateFolder(assignment_base + "_tests/", false);
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
}
void monad::find_base_dir(const char * argv0)
{
EXIT_IF_ERROR(argv0 == NULL);
size_t argv0len = strlen(argv0);
char * dir = new char[argv0len + 1];
strncpy(dir, argv0, argv0len);
dir[argv0len] = '\0';
size_t i = argv0len + 1;
do
{
i--;
if (argv0[i] == '/') break;
} while (i != 0);
// Change directory
if (i != 0)
{
dir[i] = '\0';
EXIT_IF_ERROR(chdir(dir));
}
delete [] dir;
// Ensure the dir is correct
if (!exists("./.monadid"))
{
cerr << "Could not find monad directory. "
"Please run ./monad from the directory it is located in."
<< endl;
exit(-1);
}
}
void monad::copyRequiredFiles()
{
// Clear out the temp testing folder
if (opts::clean)
forceRemoveDir(gradeFolder);
else
{
forceRemoveDir(tempFolder);
EXIT_IF_ERROR(rename(gradeFolder.c_str(), tempFolder.c_str()));
}
exec("/bin/mkdir",gradeFolder.c_str());
// Copy and link appropriate files - parsed from config.ini
importFiles(tempFolder, "./", gradeFolder, config["Monad Files"]);
importFiles(tempFolder, testsFolder, gradeFolder, config["Testing Files"]);
importFiles("", sourceFolder, gradeFolder, config["Required Files"]);
importFiles(tempFolder, "", gradeFolder, config["Preserved Files"]);
forceRemoveDir(tempFolder);
getLibs(config["Libraries"]);
}
void monad::importFiles(const string & preservedFolder, const string & theSourceFolder,
const string & destFolder, const vector<string> & files)
{
// 0 for student errors for missing Required Files dir
int student_error_code = ((preservedFolder == "") ? 0 : -1);
assertExists(destFolder);
if (preservedFolder != "") assertExists(preservedFolder);
if (theSourceFolder != "") assertExists(theSourceFolder, student_error_code);
for (size_t i = 0; i < files.size(); i++)
{
string preservedFile = preservedFolder + files[i];
string destFile = destFolder + files[i];
// Move the file from it's preservation instance
if (preservedFolder != "" && exists(preservedFile) &&
(theSourceFolder == "" || (permissions(preservedFile) & S_IWUSR) == 0))
{
//!! cout << "mv " << preservedFile << ' ' << destFile << endl;
EXIT_IF_ERROR(rename(preservedFile.c_str(), destFile.c_str()));
}
assertExists(sourceFile, student_error_code);
// Remove hacky call to exec here...
EXIT_IF_ERROR(exec("cp", "-RL", sourceFile.c_str(), destFile.c_str()) != 0,
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
"cp " + sourceFile + " " + destFile + " failed");
}
else continue;
// TODO (toole1) Protect files. This is hacky and should be in util
// TODO (toole1) this should also be AFTER Pre-Make Commands
// EXIT_IF_ERROR(chmod(destFile.c_str(),
// S_IRUSR | (permissions(destFile) & S_IXUSR)) != 0);
}
}
void monad::getLibs(const vector<string> & libs)
{
for (size_t lib_i = 0; lib_i < libs.size(); lib_i++)
{
string folder = updateFolder(libs[lib_i], false);
protectDir(folder);
chdir(gradeFolder.c_str());
system(("/bin/ln -s ../"+folder+"* ./").c_str());
chdir("..");
}
}
string monad::updateFolder(const string & folder, bool link)
{
string get = getFolder(folder, link);
if (opts::update)
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
return get;
}
string monad::getFolder(const string & folder, bool link)
{
// Look in the current folder
string target = "./" + folder;
if (exists(target + "/"))
return target + "/";
// Look in the parent folder
string source = "../" + folder;
if (exists(source + "/"))
{
if (!link) return source + "/";
EXIT_IF_ERROR(symlink(source.c_str(), target.c_str()) != 0);
return target + "/";
}
// Maybe it actually *is* the parent folder
string cwd = getcwdstr();
size_t cwd_i = findNthLast(cwd, '/', 2);
if (cwd_i != string::npos &&
cwd.length() - cwd_i > folder.length() &&
cwd.compare(cwd_i, folder.length(), folder) == 0)
{
if (!link) return "../";
EXIT_IF_ERROR(symlink("../", target.c_str()) != 0);
return target + "/";
}
// Look two directories up and over - why not? If the parent folder is
// the target source folder for the mp/lab, then the tests or libs
// may be two up and over
source = "../../" + folder;
if (exists(source + "/"))
{
if (!link) return source + "/";
EXIT_IF_ERROR(symlink(source.c_str(), target.c_str()) != 0);
return target + "/";
}
// Check Subversion
const char * svn_config_name = NULL;
const char * svn_subdir = NULL;
if (!opts::staff)
{
svn_config_name = "SVN Root";
svn_subdir = "/_public/";
}
else
{
svn_config_name = "Staff SVN";
svn_subdir = "/_current/";
}
if (!config[svn_config_name].empty())
{
string svndir = config[svn_config_name][0] + svn_subdir + folder;
// TODO (toole1): Won't work if user needs to type password
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
if (svnstatus == 0) return target + "/";
}
cerr << "Error: " << folder << " not found." << endl;
exit(-1);
return "";
}
// Execute a monad or command line command
void monad::exec_command(const string & command)
{
vector<string> args = tokenize(command, ' ');
// Allow processing of special internals
if (args[0] == "rename_main")
{
EXIT_IF_ERROR(args.size() != 3, "rename_main must take 2 arguments: a file and a new name");
rename_main(args[1], args[2]);
return;
}
#if DEBUG
cout << "Pre-Make Command: " << command << endl;
#endif
system(command.c_str());
}