-
toole1 authored
git-svn-id: https://subversion.cs.illinois.edu/svn/cs225@3366 6fbd10e7-183d-0410-a318-cb416676e4f2
toole1 authoredgit-svn-id: https://subversion.cs.illinois.edu/svn/cs225@3366 6fbd10e7-183d-0410-a318-cb416676e4f2
monad.cpp 13.03 KiB
// monad
// For illinois.edu CS 225 spring 2011
// By Jack Toole
#include <iostream>
#include <fstream>
#include <map>
#include <sstream>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <time.h>
#include <unistd.h>
#include <vector>
#include "util.h"
#include "output.h"
using namespace std;
using namespace util;
using namespace output;
namespace monad
{
void find_base_dir(const char * argv0);
void printHelp();
void printInfo();
void processArgs(int argc, char ** argv);
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 & sourceFolder,
const string & destFolder, const vector<string> & files);
void exec_command(const string & command);
string name;
string testsFolder;
string sourceFolder;
string gradeFolder;
string tempFolder;
FileMap config;
int8_t mp_part;
const int8_t no_mp_part = -1;
namespace opts {
bool clean = true;
bool update = true;
bool staff = false;
#if OPTIMIZE
bool optimize = true;
#else
bool optimize = false;
#endif
}
namespace version {
const char * official_name = "CS 225 Monad";
const char * version_name = "awakening";
const char * date = "15 July 2011";
}
}
string kyleGetDate() {
string dateAndTime = "Current Date and Time: ";
time_t currentTime;
struct tm * timeStructure;
time(¤tTime);
timeStructure = localtime(¤tTime);
dateAndTime += asctime(timeStructure);
return dateAndTime;
}
int main(int argc, char ** argv)
{
using namespace monad;
// 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 << version::official_name << endl;
cout << "Testing " << name << "..." << endl << 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 runtests
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";
if (opts::optimize)
makestr += " OPTIMIZE=on";
if (!config["Make Options"].empty())
makestr += " " + config["Make Options"][0];
#if DEBUG
cout << makestr <<endl;
#endif
// Compile with make
system(makestr.c_str()); // yes, system is insecure if the user has control
// over config.ini. But students don't.
cout << endl << endl;
int score = exec("./runtests");
if (score < 0)
{
output::header("Running tests");
cout << "Could not execute test cases" << endl << endl;
score = 0;
cout << endl;
output::total_score(score);
}
return score;
}
void monad::processArgs(int argc, char ** argv)
{
// Create OptionsMap for options and vector for positional arguments:
OptionsMap options;
vector<string> args;
// Add our possible options to our map
options.insert(make_pair("solution", false));
options.insert(make_pair("newtests", true));
options.insert(make_pair("clean", opts::clean));
options.insert(make_pair("update", opts::update));
options.insert(make_pair("staff", false));
options.insert(make_pair("optimize", opts::optimize));
options.insert(make_pair("help", false));
options.insert(make_pair("h", false));
options.insert(make_pair("info", false));
// Read in options and arguments
char * badopt = processOptions(argc, argv, options, args);
if (badopt != NULL)
exit(-1);
// Save out options
bool solution = options["solution"];
bool newtests = options["newtests"];
opts::clean = options["clean"];
opts::update = options["update"];
opts::staff = options["staff"];
// Help
if (options["help"] || options["h"] ||
(!args.empty() && strcasecmp(args[0].c_str(), "help") == 0))
{
printHelp();
exit(0);
}
// Info
if (options["info"])
{
printInfo();
exit(0);
}
// Check argument list length
if (args.empty() || args.size() > 1)
{
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(args[0], '.');
name = splitname[0];
if (splitname.size() == 1)
mp_part = no_mp_part;
else
mp_part = atoi(splitname[1].c_str());
// Clean
if (name == "clean")
{
system("/bin/rm -rf *_grade/ *_tests/ *_newtests/ *_solution/");
exit(0);
}
gradeFolder = "./" + name + "_grade/";
if (!exists(gradeFolder)) opts::clean = true;
if (opts::clean)
tempFolder = "";
else
tempFolder = "./" + name + "_temp/";
// Find source folder (i.e. ../mp1)
if (solution)
sourceFolder = updateFolder(name + "_solution/", false);
else
sourceFolder = getFolder(name + '/', false);
// tests folder
if (newtests)
testsFolder = updateFolder(name + "_newtests/", false);
else
testsFolder = updateFolder(name + "_tests/", false);
}
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::printHelp()
{
cout << "Usage: monad ASSIGNMENT [solution]" << endl
<< "Runs the tests for ASSIGNMENT (mp1, lab01, ...)" << endl
<< "\'../ASSIGNMENT/\' must exist" << endl
<< "If \'../ASSIGNMENT_tests/\' or any necessary library directories do not exist, these will be downloaded to ./ from SVN" << endl << endl
<< "monad settings and individual test settings are stored in config.ini files" << endl
<< "The following options are available for monad's ./config.ini:" << endl
<< "[SVN Root]" << endl
<< "; svn base url here (http://.../cs225/)" << endl
<< "[Monad Files]" << endl
<< "; Any files to be copied from monad ./ to ./ASSIGNMENT/ testing directory" << endl
<< "; By default, runtests.cpp is used to run test cases:" << endl
<< "runtests.cpp" << endl << endl
<< "The following options are available for individual tests' ../ASSIGNMENT_tests/config.ini:" << endl
<< "[Required Files]" << endl
<< "; Any files that must be copied from the ../ASSIGNMENT directory" << endl
<< "main.cpp" << endl
<< "[Testing Files]" << endl
<< "; Any files that must be copied from ../ASSIGNMENT_tests" << endl
<< "; By default, unit_tests.cpp contains the test cases" << endl
<< "unit_tests.cpp" << endl
<< "[Libraries]" << endl
<< "; Any external library folders to be present in the same directory as the" << endl
<< "; testing directory. These should be mirrored in [SVN Root]/_public/" << endl
<< "; The 'testutil' library in this directory is also available" << endl
<< "EasyBMP" << endl
<< endl;
}
void monad::printInfo()
{
cout << version::official_name << endl
<< "Version " << version::version_name << endl
<< "Released " << version::date << endl
<< "Developed by Jack Toole Spring/Fall 2011" << endl
<< "Copyright 2011 Jack Toole" << endl
<< "Full rights granted to Jack Toole. Rights to use and modify granted to" << endl
<< "University of Illinois Urbana-Champaign Computer Science Data Structures" << endl
<< "instructors and course staff" << endl;
}
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 & sourceFolder,
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 (sourceFolder != "") assertExists(sourceFolder, 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) &&
(sourceFolder == "" || (permissions(preservedFile) & S_IWUSR) == 0))
{
//!! cout << "mv " << preservedFile << ' ' << destFile << endl;
EXIT_IF_ERROR(rename(preservedFile.c_str(), destFile.c_str()));
}
else if (sourceFolder != "")
{
// copy the file from it's source
string sourceFile = sourceFolder + files[i];
assertExists(sourceFile, student_error_code);
//!! cout << "cp " << sourceFile << ' ' << destFile << endl;
EXIT_IF_ERROR(exec("/bin/cp", sourceFile.c_str(), destFile.c_str()) != 0);
}
else continue;
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)
exec(-1, "/usr/bin/svn","up", get.c_str());
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;
int svnstatus = exec("/usr/bin/svn","co",svndir.c_str()); // No redirect, need user to type password
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;
}
system(command.c_str());
}