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 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 << '.' << (int)mp_part;
cout << "..." << endl;
cout << getRandomQuote() << endl;
cout << endl;
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
create_mp_part_file();
// Change inclusion guards in proxy.h
exec("sed", "-i", "-e", "s/PROXY_H/PROXY_" + lexical_cast<string>(urand()) + "_H/g", "./proxy.h");
// 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;
vector<string> args;
if (opts::verbose) args.push_back("--verbose");
if (opts::valgrind) args.push_back("--valgrind");
if (!opts::buffer) args.push_back("--noredirect");
int score = exec("./proxy", args);
// 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::create_mp_part_file()
{
ofstream mp_part_file("mp_part.h");
mp_part_file << "#ifndef MONAD_SPLIT_LINE_NUMBER" << endl;
mp_part_file << "#define MONAD_SPLIT_LINE_NUMBER -1" << endl;
mp_part_file << "#endif" << endl;
mp_part_file << "#define MONAD_SPLIT (MONAD_SPLIT_LINE_NUMBER == __LINE__ || MONAD_SPLIT_LINE_NUMBER == -1)" << endl;
mp_part_file << "#define NO_MP_PART " << static_cast<int>(no_mp_part) << endl;
mp_part_file << "#define MP_PART_NUMBER ";
if (mp_part == no_mp_part) mp_part_file << "NO_MP_PART"; else mp_part_file << static_cast<int>(mp_part);
mp_part_file << endl;
mp_part_file << "#define MP_PART(x) (MP_PART_NUMBER == (x) || MP_PART_NUMBER == NO_MP_PART)" << endl;
mp_part_file.close();
}
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("buffer", opts::buffer);
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);
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
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
}
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,
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
"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)
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
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
string svndir = config[svn_config_name][0] + "/" + config[svn_config_subdir][0] + "/" + folder;
// TODO (toole1): Won't work if user needs to type password
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
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());
}