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 tryGetFolder(string dirAndFolder, const string & target, bool link);
string getFolder(const string & folder, const string & searchDir = "", bool link = false);
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
}
}
{
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
if (assignment[assignment.length() - 1] == '/')
assignment = assignment.substr(0, assignment.length() - 1);
size_t slash_i = assignment.find_last_of('/');
string assignment_name = (slash_i == string::npos) ? assignment : assignment.substr(slash_i + 1);
string assignment_dir = (slash_i == string::npos) ? "" : assignment.substr(0, slash_i);
// TODO (toole1): All this stuff is ugly!
vector<string> splitname = tokenize(assignment_name, '.');
if (!exists(gradeFolder)) opts::clean = true;
if (opts::clean)
tempFolder = "";
else
if (opts::solution)
sourceFolder = updateFolder(assignment_base + "_solution/", false);
sourceFolder = getFolder(assignment_base + '/', assignment_dir, false);
testsFolder = updateFolder(assignment_base + "_provided/", false);
testsFolder = updateFolder(assignment_base + "_newtests/", false);
testsFolder = updateFolder(assignment_base + "_tests/", false);
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
}
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()));
}
// 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,
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
"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 monad::tryGetFolder(string dirAndFolder, const string & target, bool link)
if (dirAndFolder[dirAndFolder.length() - 1] == '/')
dirAndFolder = dirAndFolder.substr(0, dirAndFolder.size() - 1);
if (!link) return dirAndFolder + "/";
EXIT_IF_ERROR(symlink(dirAndFolder.c_str(), target.c_str()) != 0);
return target + "/";
string monad::getFolder(const string & folder, const string & searchDir /* = "" */, bool link /* = false */)
{
// TODO (toole1): All this stuff is ugly!
string target = "./" + folder;
string result;
// Look in the searchDir folder
if (searchDir != "")
result = tryGetFolder(searchDir + "/" + folder, target, link);
if (result != "") return result;
// Try SVN
// Clear out our cache first
if (exists(target + "/"))
exec("rm", "-rf", target + "/");
string svndir = config["SVN Root"][0] + "/" + searchDir + "/" + folder;
int svnstatus = exec("svn", "co", svndir /*, NULL*/);
if (svnstatus == 0) return target + "/";
cerr << "Error: " << folder << " not found." << endl;
exit(-1);
// Look in the parent folder
result = tryGetFolder("../" + folder, target, link);
if (result != "") return result;
// Look in the current folder
if (exists(target + "/"))
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
// This also covers the case where it is our parent folder
result = tryGetFolder("../../" + folder, target, link);
if (result != "") return result;
string svndir = config[svn_config_name][0] + "/" + config[svn_config_subdir][0] + "/" + folder;
// TODO (toole1): Won't work if user needs to type password
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
if (svnstatus == 0) return target + "/";
}
cerr << "Error: " << folder << " not found." << endl;
exit(-1);
}
// 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());
}