Skip to content
Snippets Groups Projects
Commit 9809c20d authored by toole1's avatar toole1
Browse files

jack renaming monad to monad_dev

git-svn-id: https://subversion.cs.illinois.edu/svn/cs225@4324 6fbd10e7-183d-0410-a318-cb416676e4f2
parent 454ef9dd
No related branches found
No related tags found
No related merge requests found
CC = g++
CFLAGS = -Wall
EXENAME = monad
OBJS = $(EXENAME).o help.o util.o monad_shared.o
IDFILE = .monadid
OPTIMIZE = off
ifeq ($(strip $(OPTIMIZE)),on)
CFLAGS += -O2 -DOPTIMIZE
else ifeq ($(strip $(OPTIMIZE)),off)
CFLAGS += -g -O0
else
$(warning Invalid value specified for OPTIMIZE. Should be on or off)
CFLAGS += -g -O0
endif
all: $(EXENAME) $(IDFILE)
$(IDFILE):
echo -e 'This file is used for $(EXENAME) directory identification\n$(EXENAME) Build Date:' `date`> $(IDFILE)
$(EXENAME): $(OBJS) $(IDFILE)
$(CC) $(CFLAGS) $(OBJS) -o $@
help.cpp: $(wildcard *.h) README.cpp
README.cpp: README.txt
sed -e 's/"/\\"/g' -e 's/^/ << "/' -e 's/$$/" << endl/' README.txt > README.cpp
.cpp.o: $(wildcard *.h)
$(CC) $(CFLAGS) -c $(@:.o=.cpp) -o $@
.PHONY: clean
clean:
rm -f *.o README.cpp $(EXENAME) $(IDFILE) $(wildcard $(EXENAME)$(EXENAME)*)
.PHONY: clean.sh
clean.sh: $(EXENAME)
./clean.sh
CC = g++
CFLAGS += -Wall
TESTEXE := proxy
# This order is necessary for security. Always include student code last!
TESTOBJS := $(TESTEXE).o util.o unit_tests.o monad_shared.o $(TESTOBJS)
OPTIMIZE := off
ifeq ($(strip $(OPTIMIZE)),on)
CFLAGS += -O2 -DOPTIMIZE
else ifeq ($(strip $(OPTIMIZE)),off)
CFLAGS += -g -O0
else
$(warning Invalid value specified for OPTIMIZE. Should be on or off)
CFLAGS += -g -O0
endif
ifndef ALL_TARGET
ALL_TARGET = all
all: $(TESTEXE)
endif
$(TESTEXE): $(TESTOBJS)
$(CC) $(TESTOBJS) -o $@
.cpp.o: $(wildcard *.h)
$(CC) $(CFLAGS) -c $(@:.o=.cpp) -o $@
ifndef CLEAN_TARGET
CLEAN_TARGET = clean
.PHONY: clean
clean:
-rm -f *.o $(TESTEXE)
endif
Monad Autograder (c) Jack Toole
Usage: monad ASSIGNMENT [options]
Runs the tests for ASSIGNMENT (mp1, lab01, ...)
'../ASSIGNMENT/' must exist
If '../ASSIGNMENT_tests/' or any necessary library directories do not exist, these will be downloaded to ./ from SVN
monad settings and individual test settings are stored in config.ini files
The following options are available for monad's ./config.ini:
[SVN Root]
; svn base url here (http://.../cs225/)
[Monad Files]
; Any files to be copied from monad ./ to ./ASSIGNMENT/ testing directory
; By default, proxy.cpp is used to run test cases:
proxy.cpp
The following options are available for individual tests' ../ASSIGNMENT_tests/config.ini:
[Required Files]
; Any files that must be copied from the ../ASSIGNMENT directory
main.cpp
[Testing Files]
; Any files that must be copied from ../ASSIGNMENT_tests
; By default, unit_tests.cpp contains the test cases
unit_tests.cpp
[Libraries]
; Any external library folders to be present in the same directory as the
; testing directory. These should be mirrored in [SVN Root]/_public/
; The 'testutil' library in this directory is also available
EasyBMP
x Complete
v Untested
d Deleted
p Probably already done
Critical
---------------------
x alarm() like timeout for test cases
x better library support for everything
x find lab02/ if it is parent folder
x change temp directory name to lab02_grade
x better write protection
x better output design
x better cout output display. Inefficiencies may be hazardous (EasyBMP)
x [processing]
x [make options] - support multiple on same line, tough, maybe use system
x find monad/ directory
x Look into returning 0s for students who forget files
x handle 2 svn's discrepancies better, how to download _tests before release
x improve command line args
x must read from pipe while waiting
v create longer realtime timeout in addition to process time timeout (blocking)
d maybe lower realtime limit to prevent exploiting forks for cpu time - nvm
x customizable timeouts
x clean/up option for tests etc downloaded from svn
x need 2 pipes to avoid security flaw
x make valgrind secure
x remove tests from global static space
x provisions to handle expected output -> really should rework tests and MPs
x ADD --update to indicate to update any svn folders
avoid wasted compilation - Simon
x better sandboxing - move directory, make new, move safe files, rm temp
x [Preserved Files]
x library make system - fix the dependency situation in [Preserved Files]
p better library system, but what?
p this is a hard problem because of library system issues - EasyBMP.h
x EasyBMP shouldn't be taken from student's folder
SSH for security
/dev/shm
better output
personification
quotes of the day
man isatty - colors when terminal
better output on student idiocy
x on crazy memory errors, glibc issues, valgrind aborts
x fix "" failure messages
x on EXIT_IF_ERROR crashes
if compilation fails
STYLE
Directory structure for config.ini includes
encrypt/secure pipe transfers
x safer cross-platform exec()
x setenv("PATH"...) to safe path and use execlp
remove all system() calls
fix make system() call in monad.cpp - need some varargs or something
replace ln -s util/* system() calls
Moderately important
---------------------
x fix valgrind output on actual test failure
x allow running of new tests
x warn, not error on incorrect sum!
p make sure all student errors return code 0
x change timeout to use ms as units like time
x Fail valgrind tests on EasyBMP warnings -> Must explicitly declare
x add mechanisms for custom output checks
fix sigalarm security hole
optimization
mp1_newtests and lab02_newtests currently have 2 different ways of compiling 2 programs. this should be unified
more elegant way of handling mp4's Makefile.1 for part 1 in config.ini
possibly have the MP part number be defined when calling make and handle it internally.
Doesn't really matter
---------------------
x spacing between longest test and points should be cut from [1 or 2] to 1.
d processsOptions errors
fix unknown times, maybe only output timeout
remove additional output under valgrind
readFile() bug where lines have ':' or '?' in them?!
x fix immediate bug
long term style solution
#!/bin/bash
EXENAME=monad
for i in {1..10}
do
starname=$EXENAME
j=1
while [ $j -le $i ]
do
starname="${starname}${EXENAME}"
j=$[j+1]
done
#echo ${starname}
cp "${EXENAME}" "${starname}"
done
[SVN Root]
:https://subversion.ews.illinois.edu/svn/sp11-cs225/
[Staff SVN]
:https://subversion.cs.illinois.edu/svn/cs225/
[Monad Files]
Makefile.proxy
memcheck.h
monad_shared.h
monad_shared.cpp
proxy.h
proxy.cpp
valgrind.h
[Libraries]
util
#include "monad.h"
namespace monad
{
void printHelp()
{
cout
#include "README.cpp"
;
}
}
/*
----------------------------------------------------------------
Notice that the following BSD-style license applies to this one
file (memcheck.h) only. The rest of Valgrind is licensed under the
terms of the GNU General Public License, version 2, unless
otherwise indicated. See the COPYING file in the source
distribution for details.
----------------------------------------------------------------
This file is part of MemCheck, a heavyweight Valgrind tool for
detecting memory errors.
Copyright (C) 2000-2010 Julian Seward. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. The origin of this software must not be misrepresented; you must
not claim that you wrote the original software. If you use this
software in a product, an acknowledgment in the product
documentation would be appreciated but is not required.
3. Altered source versions must be plainly marked as such, and must
not be misrepresented as being the original software.
4. The name of the author may not be used to endorse or promote
products derived from this software without specific prior written
permission.
THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS
OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
----------------------------------------------------------------
Notice that the above BSD-style license applies to this one file
(memcheck.h) only. The entire rest of Valgrind is licensed under
the terms of the GNU General Public License, version 2. See the
COPYING file in the source distribution for details.
----------------------------------------------------------------
*/
#ifndef __MEMCHECK_H
#define __MEMCHECK_H
/* This file is for inclusion into client (your!) code.
You can use these macros to manipulate and query memory permissions
inside your own programs.
See comment near the top of valgrind.h on how to use them.
*/
#include "valgrind.h"
/* !! ABIWARNING !! ABIWARNING !! ABIWARNING !! ABIWARNING !!
This enum comprises an ABI exported by Valgrind to programs
which use client requests. DO NOT CHANGE THE ORDER OF THESE
ENTRIES, NOR DELETE ANY -- add new ones at the end. */
typedef
enum {
VG_USERREQ__MAKE_MEM_NOACCESS = VG_USERREQ_TOOL_BASE('M','C'),
VG_USERREQ__MAKE_MEM_UNDEFINED,
VG_USERREQ__MAKE_MEM_DEFINED,
VG_USERREQ__DISCARD,
VG_USERREQ__CHECK_MEM_IS_ADDRESSABLE,
VG_USERREQ__CHECK_MEM_IS_DEFINED,
VG_USERREQ__DO_LEAK_CHECK,
VG_USERREQ__COUNT_LEAKS,
VG_USERREQ__GET_VBITS,
VG_USERREQ__SET_VBITS,
VG_USERREQ__CREATE_BLOCK,
VG_USERREQ__MAKE_MEM_DEFINED_IF_ADDRESSABLE,
/* Not next to VG_USERREQ__COUNT_LEAKS because it was added later. */
VG_USERREQ__COUNT_LEAK_BLOCKS,
/* This is just for memcheck's internal use - don't use it */
_VG_USERREQ__MEMCHECK_RECORD_OVERLAP_ERROR
= VG_USERREQ_TOOL_BASE('M','C') + 256
} Vg_MemCheckClientRequest;
/* Client-code macros to manipulate the state of memory. */
/* Mark memory at _qzz_addr as unaddressable for _qzz_len bytes. */
#define VALGRIND_MAKE_MEM_NOACCESS(_qzz_addr,_qzz_len) \
VALGRIND_DO_CLIENT_REQUEST_EXPR(0 /* default return */, \
VG_USERREQ__MAKE_MEM_NOACCESS, \
(_qzz_addr), (_qzz_len), 0, 0, 0)
/* Similarly, mark memory at _qzz_addr as addressable but undefined
for _qzz_len bytes. */
#define VALGRIND_MAKE_MEM_UNDEFINED(_qzz_addr,_qzz_len) \
VALGRIND_DO_CLIENT_REQUEST_EXPR(0 /* default return */, \
VG_USERREQ__MAKE_MEM_UNDEFINED, \
(_qzz_addr), (_qzz_len), 0, 0, 0)
/* Similarly, mark memory at _qzz_addr as addressable and defined
for _qzz_len bytes. */
#define VALGRIND_MAKE_MEM_DEFINED(_qzz_addr,_qzz_len) \
VALGRIND_DO_CLIENT_REQUEST_EXPR(0 /* default return */, \
VG_USERREQ__MAKE_MEM_DEFINED, \
(_qzz_addr), (_qzz_len), 0, 0, 0)
/* Similar to VALGRIND_MAKE_MEM_DEFINED except that addressability is
not altered: bytes which are addressable are marked as defined,
but those which are not addressable are left unchanged. */
#define VALGRIND_MAKE_MEM_DEFINED_IF_ADDRESSABLE(_qzz_addr,_qzz_len) \
VALGRIND_DO_CLIENT_REQUEST_EXPR(0 /* default return */, \
VG_USERREQ__MAKE_MEM_DEFINED_IF_ADDRESSABLE, \
(_qzz_addr), (_qzz_len), 0, 0, 0)
/* Create a block-description handle. The description is an ascii
string which is included in any messages pertaining to addresses
within the specified memory range. Has no other effect on the
properties of the memory range. */
#define VALGRIND_CREATE_BLOCK(_qzz_addr,_qzz_len, _qzz_desc) \
VALGRIND_DO_CLIENT_REQUEST_EXPR(0 /* default return */, \
VG_USERREQ__CREATE_BLOCK, \
(_qzz_addr), (_qzz_len), (_qzz_desc), \
0, 0)
/* Discard a block-description-handle. Returns 1 for an
invalid handle, 0 for a valid handle. */
#define VALGRIND_DISCARD(_qzz_blkindex) \
VALGRIND_DO_CLIENT_REQUEST_EXPR(0 /* default return */, \
VG_USERREQ__DISCARD, \
0, (_qzz_blkindex), 0, 0, 0)
/* Client-code macros to check the state of memory. */
/* Check that memory at _qzz_addr is addressable for _qzz_len bytes.
If suitable addressibility is not established, Valgrind prints an
error message and returns the address of the first offending byte.
Otherwise it returns zero. */
#define VALGRIND_CHECK_MEM_IS_ADDRESSABLE(_qzz_addr,_qzz_len) \
VALGRIND_DO_CLIENT_REQUEST_EXPR(0, \
VG_USERREQ__CHECK_MEM_IS_ADDRESSABLE, \
(_qzz_addr), (_qzz_len), 0, 0, 0)
/* Check that memory at _qzz_addr is addressable and defined for
_qzz_len bytes. If suitable addressibility and definedness are not
established, Valgrind prints an error message and returns the
address of the first offending byte. Otherwise it returns zero. */
#define VALGRIND_CHECK_MEM_IS_DEFINED(_qzz_addr,_qzz_len) \
VALGRIND_DO_CLIENT_REQUEST_EXPR(0, \
VG_USERREQ__CHECK_MEM_IS_DEFINED, \
(_qzz_addr), (_qzz_len), 0, 0, 0);
/* Use this macro to force the definedness and addressibility of an
lvalue to be checked. If suitable addressibility and definedness
are not established, Valgrind prints an error message and returns
the address of the first offending byte. Otherwise it returns
zero. */
#define VALGRIND_CHECK_VALUE_IS_DEFINED(__lvalue) \
VALGRIND_CHECK_MEM_IS_DEFINED( \
(volatile unsigned char *)&(__lvalue), \
(unsigned long)(sizeof (__lvalue)))
/* Do a full memory leak check (like --leak-check=full) mid-execution. */
#define VALGRIND_DO_LEAK_CHECK \
{unsigned long _qzz_res; \
VALGRIND_DO_CLIENT_REQUEST(_qzz_res, 0, \
VG_USERREQ__DO_LEAK_CHECK, \
0, 0, 0, 0, 0); \
}
/* Do a summary memory leak check (like --leak-check=summary) mid-execution. */
#define VALGRIND_DO_QUICK_LEAK_CHECK \
{unsigned long _qzz_res; \
VALGRIND_DO_CLIENT_REQUEST(_qzz_res, 0, \
VG_USERREQ__DO_LEAK_CHECK, \
1, 0, 0, 0, 0); \
}
/* Return number of leaked, dubious, reachable and suppressed bytes found by
all previous leak checks. They must be lvalues. */
#define VALGRIND_COUNT_LEAKS(leaked, dubious, reachable, suppressed) \
/* For safety on 64-bit platforms we assign the results to private
unsigned long variables, then assign these to the lvalues the user
specified, which works no matter what type 'leaked', 'dubious', etc
are. We also initialise '_qzz_leaked', etc because
VG_USERREQ__COUNT_LEAKS doesn't mark the values returned as
defined. */ \
{unsigned long _qzz_res; \
unsigned long _qzz_leaked = 0, _qzz_dubious = 0; \
unsigned long _qzz_reachable = 0, _qzz_suppressed = 0; \
VALGRIND_DO_CLIENT_REQUEST(_qzz_res, 0, \
VG_USERREQ__COUNT_LEAKS, \
&_qzz_leaked, &_qzz_dubious, \
&_qzz_reachable, &_qzz_suppressed, 0); \
leaked = _qzz_leaked; \
dubious = _qzz_dubious; \
reachable = _qzz_reachable; \
suppressed = _qzz_suppressed; \
}
/* Return number of leaked, dubious, reachable and suppressed bytes found by
all previous leak checks. They must be lvalues. */
#define VALGRIND_COUNT_LEAK_BLOCKS(leaked, dubious, reachable, suppressed) \
/* For safety on 64-bit platforms we assign the results to private
unsigned long variables, then assign these to the lvalues the user
specified, which works no matter what type 'leaked', 'dubious', etc
are. We also initialise '_qzz_leaked', etc because
VG_USERREQ__COUNT_LEAKS doesn't mark the values returned as
defined. */ \
{unsigned long _qzz_res; \
unsigned long _qzz_leaked = 0, _qzz_dubious = 0; \
unsigned long _qzz_reachable = 0, _qzz_suppressed = 0; \
VALGRIND_DO_CLIENT_REQUEST(_qzz_res, 0, \
VG_USERREQ__COUNT_LEAK_BLOCKS, \
&_qzz_leaked, &_qzz_dubious, \
&_qzz_reachable, &_qzz_suppressed, 0); \
leaked = _qzz_leaked; \
dubious = _qzz_dubious; \
reachable = _qzz_reachable; \
suppressed = _qzz_suppressed; \
}
/* Get the validity data for addresses [zza..zza+zznbytes-1] and copy it
into the provided zzvbits array. Return values:
0 if not running on valgrind
1 success
2 [previously indicated unaligned arrays; these are now allowed]
3 if any parts of zzsrc/zzvbits are not addressable.
The metadata is not copied in cases 0, 2 or 3 so it should be
impossible to segfault your system by using this call.
*/
#define VALGRIND_GET_VBITS(zza,zzvbits,zznbytes) \
VALGRIND_DO_CLIENT_REQUEST_EXPR(0, \
VG_USERREQ__GET_VBITS, \
(char*)(zza), (char*)(zzvbits), \
(zznbytes), 0, 0)
/* Set the validity data for addresses [zza..zza+zznbytes-1], copying it
from the provided zzvbits array. Return values:
0 if not running on valgrind
1 success
2 [previously indicated unaligned arrays; these are now allowed]
3 if any parts of zza/zzvbits are not addressable.
The metadata is not copied in cases 0, 2 or 3 so it should be
impossible to segfault your system by using this call.
*/
#define VALGRIND_SET_VBITS(zza,zzvbits,zznbytes) \
VALGRIND_DO_CLIENT_REQUEST_EXPR(0, \
VG_USERREQ__SET_VBITS, \
(char*)(zza), (char*)(zzvbits), \
(zznbytes), 0, 0 )
#endif
// monad
// For illinois.edu CS 225 spring 2011
// By Jack Toole
#include "monad.h"
namespace monad
{
void find_base_dir(const char * argv0);
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
}
}
string kyleGetDate() {
string dateAndTime = "Current Date and Time: ";
time_t currentTime;
struct tm * timeStructure;
time(&currentTime);
timeStructure = localtime(&currentTime);
dateAndTime += asctime(timeStructure);
return dateAndTime;
}
int main(int argc, char ** argv)
{
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 << 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 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 --quiet --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.
// 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;
int score = exec("./proxy");
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::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());
}
#ifndef MONAD_H
#define MONAD_H
#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 "monad_shared.h"
using namespace std;
using namespace util;
using namespace monad_shared;
namespace monad
{
void printHelp();
}
#endif // MONAD_H
#include <iostream>
#include "monad_shared.h"
#include "util.h"
using namespace monad_shared;
using namespace util;
using std::cout;
using std::cerr;
using std::endl;
namespace monad_shared
{
namespace version
{
const char * official_name = "CS 225 Monad";
const char * version_name = "awakening";
const char * date = "15 July 2011";
}
const char * unit_test::pass_string = "~~PASSED~~";
void printInfo()
{
cout << endl
<< 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
<< endl;
}
namespace output
{
// Set EXIT_IF_ERROR message
void set_error_message()
{
SET_ERROR_MESSAGE("Oops! Something went wrong inside of me.\n"
"Please contact course staff with the following error details, and they'll figure it out:\n");
}
void header(const string & title)
{
cout << title << "..." << endl
<< "================================" << endl;
}
void warning(const string & message)
{
cerr << endl
<< "********************************"
"********************************" << endl
<< "WARNING!" << endl
<< message << endl
<< "********************************"
"********************************" << endl << endl;
}
void total_score(int32_t score)
{
output::header("Total score (out of 100)");
cout << "TOTAL SCORE: " << score << endl << endl;
}
void testname(const unit_test & curr_test, int32_t max_testname_len, int32_t max_points_len)
{
// Output test name
int32_t pos = 0; // keep track of cursor position
std::cout << curr_test.name << ' ';
pos += strlen(curr_test.name) + 1;
if (curr_test.is_valgrind)
{
cout << "(valgrind) ";
pos += 11;
}
if (pos % 2 == max_testname_len % 2)
{
cout << ' ';
pos++;
}
while (pos < max_testname_len + 1)
{
cout << ". ";
pos += 2;
}
pos = 0; // reset column
std::cout << "[" << curr_test.points << " pts] ";
pos += intlen(curr_test.points) + 7;
while (pos < max_points_len + 7)
{
cout << ' ';
pos++;
}
cout << "- ";
}
void detailed_info(const unit_test & curr_test)
{
std::cout << "--------------------------------" << endl
<< curr_test.name;
if (curr_test.is_valgrind) std::cout << " (run under valgrind)";
std::cout << " [" << curr_test.points << " points]" << endl;
const string & error = curr_test.errormsg;
const string & output = curr_test.output;
if (curr_test.passed())
std::cout << "Result: passed" << endl;
else
std::cout << "Result: FAILED: " << error << endl;
if (curr_test.time < 0)
cout << "Took unknown time (";
else
cout << "Took " << curr_test.time << "ms (";
cout << curr_test.timeout << "ms timeout)" << endl;
std::cout << "Output:" << endl
<< "--------------------------------" << endl;
// Tab the output over to distinguish it from the test case
if (output != "")
{
//std::cout << " ";
//replaceAllInternal(output,"\n","\n ");
std::cout << output;
if (output[output.length() - 1] != '\n') std::cout << endl;
}
cout << endl;
}
} // namespace output
} // namespace monad_shared
#ifndef MONAD_SHARED
#define MONAD_SHARED
#include "util.h"
#include "pipestream.h"
namespace monad_shared
{
namespace version
{
extern const char * official_name;
extern const char * version_name;
extern const char * date;
}
void printInfo();
struct unit_test
{
typedef std::string return_type;
typedef return_type (*function)(unit_test & this_test);
static const char * pass_string;
std::string errormsg;
std::string output;
function func;
const char * name;
util::pipestream * checkstream;
long timeout;
long time;
int32_t points;
int32_t valgrind_flags;
int8_t mp_part;
bool is_valgrind;
unit_test(int8_t MPpart_,
const char * name_,
unit_test::function func_,
int32_t points_,
long timeout_,
bool is_valgrind_)
: errormsg("message not set / proxy crashed"),
func(func_),
name(name_),
checkstream(NULL),
timeout(timeout_),
time(-1),
points(points_),
valgrind_flags(-1),
mp_part(MPpart_),
is_valgrind(is_valgrind_) { }
bool passed() const
{
return errormsg == pass_string;
}
};
namespace output
{
void set_error_message();
void header(const std::string & title);
void total_score(int32_t score);
void warning(const std::string & message);
void testname(const unit_test & curr_test, int32_t max_testname_len, int32_t max_points_len);
void detailed_info(const unit_test & curr_test);
} // namespace output
}
#endif // MONAD_SHARED
../util/pipestream.cpp
\ No newline at end of file
../util/pipestream.h
\ No newline at end of file
// proxy.cpp
// NOTE: This is a generic file. Actual unit tests are located in
// unit_tests.cpp.
// By Jack Toole for CS 225 spring 2011
// For strsignal:
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif
#include <signal.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/time.h>
#include <iomanip>
#include "memcheck.h"
#include "monad_shared.h"
#include "pipestream.h"
#include "proxy.h"
#include "util.h"
#include "valgrind.h"
using std::string;
using std::vector;
using std::pair;
using namespace util;
using namespace monad_shared;
namespace proxy
{
vector<unit_test> * global_tests = NULL;
output_check_map * global_output_checks = NULL;
}
OUTPUT_CHECK(equals)
{
return output == expected;
}
OUTPUT_CHECK(contains)
{
return output.find(expected) != string::npos;
}
int main(int argc, char ** argv)
{
using namespace proxy;
// set up EXIT_IF_ERROR messages
output::set_error_message();
// Set up run-time environment
RunTimeEnvironment env(global_tests, global_output_checks);
// Set up the tests
RunTests runner(argc, argv, env);
// Execute
return runner.execute();
}
namespace proxy {
// class add_unit_test
add_unit_test::add_unit_test(int8_t MPpart, const char * name, unit_test::function func,
int32_t points_in_part, int32_t points_in_total, long timeout,
bool is_valgrind)
{
assertMPpart(MPpart, name);
lazy_init_global_tests();
int32_t points = get_points(points_in_total, points_in_part);
// Add to global tests vector
global_tests->push_back(unit_test(MPpart, name, func, points, timeout, is_valgrind));
}
// Check to make global tests vector
void add_unit_test::lazy_init_global_tests()
{
if (global_tests == NULL)
global_tests = new std::vector<unit_test>;
}
// Check that test was legal to compile
void add_unit_test::assertMPpart(int8_t MPpart, const char * name)
{
if (!MP_PART(MPpart))
{
std::cerr << "Internal Error: unit tests should be surrounded by" << std::endl
<< "#if MP_PART(partNumber) ... #endif" << std::endl
<< name << "()" << " is defined for part " << (int)MPpart
<< " but compiled for part " << MP_PART_NUMBER << std::endl;
exit(-2);
}
}
// Discriminate which points value to add
int32_t add_unit_test::get_points(int32_t points_in_total, int32_t points_in_part)
{
#if MP_PART(NO_MP_PART)
return points_in_total;
#else
return points_in_part;
#endif
}
// class add_output_check
add_output_check::add_output_check(const char * name, output_check func)
{
if (global_output_checks == NULL)
global_output_checks = new output_check_map;
(*global_output_checks)[name] = func;
}
// class Run_Time_Environment
RunTimeEnvironment::RunTimeEnvironment(vector<unit_test> *& init_tests,
output_check_map *& init_output_checks)
: itimer_number0(ITIMER_PROF),
itimer_number1(ITIMER_REAL),
timeout_signum0(SIGPROF),
timeout_signum1(SIGALRM),
max_output_length(8*1024), //arbitrary
single_test_passed_string("Result: passed"),
heap_tests(init_tests),
output_checks(init_output_checks)
{
// Copy globals to the RunTimeEnvironment space
// And remove them from the global scope
static int8_t singleton = 0;
EXIT_IF_ERROR(singleton++ != 0, "There may only be one runtime environment");
EXIT_IF_ERROR(heap_tests == NULL, "No test cases found");
if (output_checks == NULL)
output_checks = new output_check_map;
init_tests = NULL;
init_output_checks = NULL;
}
int RunTimeEnvironment::cleanup_globals()
{
if (heap_tests != NULL) delete heap_tests;
if (output_checks != NULL) delete output_checks;
heap_tests = NULL;
output_checks = NULL;
return 0;
}
// class RunTests
RunTests::RunTests(int argc, char ** argv, RunTimeEnvironment & env)
: environment(env)
{
process_args(argc, argv); // sets up mode and test_arg
redirect_glibc_to_stderr();
}
void RunTests::redirect_glibc_to_stderr()
{
// Turn off glibc errors default write-to-terminal behaviour, because
// it does not get caught by stderr. This instead results in an abort.
// Unfortunately, this has still-reachable memory leaks under valgrind
if (RUNNING_ON_VALGRIND == 0)
setenv("LIBC_FATAL_STDERR_","1",1);
//setenv("MALLOC_CHECK_","2",1);
}
int32_t RunTests::execute()
{
int32_t return_code = execute_by_mode();
environment.cleanup_globals();
return return_code;
}
int32_t RunTests::execute_by_mode()
{
if (mode == SINGLE_TEST)
return run_single_test(test_arg);
else // if (mode == ALL_TESTS)
return run_all_tests();
}
void RunTests::process_args(int argc, char ** argv)
{
if (argc > 2)
{
cout << "Usage: " << argv[0] << "[testname]" << endl;
exit(0);
}
if (argc == 2 && strcasecmp(argv[1], "--info") == 0)
{
printInfo();
exit(0);
}
if (argc == 1 || strcmp(argv[1], "all") == 0)
mode = ALL_TESTS;
else
{
mode = SINGLE_TEST;
test_arg = argv[1];
}
}
int32_t RunTests::run_single_test(const char * testname)
{
vector<unit_test> & tests = *environment.heap_tests;
for (size_t test_i = 0; test_i < tests.size(); test_i++)
if (strcmp(tests[test_i].name, testname) == 0)
return run_single_test(tests[test_i]);
cout << "Test not found" << endl;
exit(-1);
}
int32_t RunTests::run_single_test(unit_test & curr_test)
{
cout << "Running " << curr_test.name << " [worth "
<< curr_test.points << " points, output below]" << endl;
bool is_parent_process = execute_test(curr_test, environment, false);
if (!is_parent_process)
return environment.cleanup_globals();
string & error = curr_test.errormsg;
handle_single_test_output(curr_test.output);
if (error == "")
error = "Unexpectedly Aborted";
if (curr_test.passed())
cout << environment.single_test_passed_string << endl;
else
cout << "Result: FAILED:" << endl << error << endl;
return curr_test.valgrind_flags;
}
void RunTests::handle_single_test_output(const string & output)
{
if (output != "")
{
cout << output;
if (output[output.size()-1] != '\n')
cout << endl;
}
}
int RunTests::run_all_tests()
{
vector<unit_test> & tests = *environment.heap_tests;
output::header("Running tests");
int32_t points_sum = get_sum_points();
int32_t max_testname_len = get_max_testname_length();
int32_t max_points_len = get_max_points_length();
if (points_sum < 100)
output::warning("Unit test scores sum to " + to_string(points_sum) +
", should be at least 100");
int32_t score = 0;
for (size_t test_i = 0; test_i < tests.size(); test_i++)
{
unit_test & curr_test = tests[test_i];
output::testname(curr_test, max_testname_len, max_points_len);
bool is_parent_process = execute_test(curr_test, environment, true);
// Check for the child process
// This is unfortunately necessary (instead of an exit) to clean up
// all the memory in use in main and the global space for valgrind
if (!is_parent_process)
return environment.cleanup_globals();
// Check for success
if (curr_test.passed())
score += curr_test.points;
output_single_test_passfail(curr_test);
}
cout << endl << endl;
output_detailed_info_if_any_failed(score);
output::total_score(score);
return score;
}
int32_t RunTests::get_sum_points()
{
vector<unit_test> & tests = *environment.heap_tests;
int32_t points_sum = 0;
for (size_t test_i = 0; test_i < tests.size(); test_i++)
points_sum += tests[test_i].points;
return points_sum;
}
int32_t RunTests::get_max_testname_length()
{
vector<unit_test> & tests = *environment.heap_tests;
int32_t max_testname_len = 0;
for (size_t test_i = 0; test_i < tests.size(); test_i++)
{
int32_t currlen = strlen(tests[test_i].name) + (int)tests[test_i].is_valgrind * 11; // strlen(" (valgrind)");
if (currlen > max_testname_len)
max_testname_len = currlen;
}
return max_testname_len;
}
int32_t RunTests::get_max_points_length()
{
vector<unit_test> & tests = *environment.heap_tests;
int32_t max_points_len = 0;
for (size_t test_i = 0; test_i < tests.size(); test_i++)
{
if (tests[test_i].points >= 100)
max_points_len = 3;
else if (tests[test_i].points >= 10)
max_points_len = 2;
}
return max_points_len;
}
void RunTests::output_detailed_info_if_any_failed(int32_t score)
{
vector<unit_test> & tests = *environment.heap_tests;
bool any_failed = false;
for (size_t test_i = 0; test_i < tests.size(); test_i++)
if (!tests[test_i].passed())
any_failed = true;
if (any_failed)
output_detailed_tests_info(score);
}
void RunTests::output_detailed_tests_info(int32_t score)
{
output::total_score(score);
cout << endl << endl;
output::header("Detailed test output");
vector<unit_test> & tests = *environment.heap_tests;
for (size_t test_i = 0; test_i < tests.size(); test_i++)
if (!tests[test_i].passed())
output::detailed_info(tests[test_i]);
cout << endl << "--------------------------------" << endl;
}
void RunTests::output_single_test_passfail(const unit_test & curr_test)
{
if (curr_test.passed())
std::cout << "passed" << endl;
else
std::cout << "FAILED: " << curr_test.errormsg << endl;
}
test_execution::test_execution(unit_test & _test, RunTimeEnvironment & env, bool enable_valgrind_call)
: test(_test), environment(env)
{
do_valgrind = enable_valgrind_call && test.is_valgrind;
if (!do_valgrind)
test.checkstream = new pipestream;
}
void test_execution::child()
{
fmsg_pipe.close_read();
cout_pipe.close_read();
nums_pipe.close_read();
// Redirect stdout/stderr to pipe
cout_pipe.steal_output(STDOUT_FILENO);
cout_pipe.steal_output(STDERR_FILENO);
if (do_valgrind)
{
child_valgrind();
}
else // if (!test.is_valgrind)
{
child_test();
}
}
void test_execution::parent()
{
fmsg_pipe.close_write();
cout_pipe.close_write();
nums_pipe.close_write();
if (test.checkstream != NULL)
test.checkstream->close_write();
// Read stdout/stderr pipe while process is running
cout_pipe >> setmax(environment.max_output_length) >> test.output;
cout_pipe.close_read();
}
void test_execution::after_success(int8_t return_code)
{
if (do_valgrind)
after_valgrind_success(return_code);
else
after_test_success();
}
void test_execution::after_failure(int8_t signal_number)
{
fmsg_pipe.close_read();
nums_pipe.close_read();
if (environment.is_timeout_signal(signal_number))
{
test.errormsg = string("Timed out") + " (" + to_string(test.timeout) + "ms)";
test.time = test.timeout;
}
else
test.errormsg = strsignal(signal_number);
}
bool RunTests::execute_test(unit_test & test, RunTimeEnvironment & environment, bool enable_valgrind_call)
{
cout << std::flush;
test_execution executor(test, environment, enable_valgrind_call);
return fork_execute(executor);
}
template <typename F>
bool fork_execute(F & executor)
{
// Fork
pid_t process_id;
process_id = fork();
EXIT_IF_ERROR(process_id < 0, "Could not fork application");
if (process_id == 0)
{
executor.child();
// Unfortunately necessary to use a return stack instead of
// exit() to get rid of valgrind errors
// (which is important if we use valgrind ./proxy recursively)
return false; // previously exit(0);
}
else // if (process_id > 0)
{
executor.parent();
int child_status;
pid_t ws = waitpid(process_id, &child_status, 0); //should return immediately
EXIT_IF_ERROR(ws == -1);
if (WIFEXITED(child_status)) /* exit code in child_status */
executor.after_success(WEXITSTATUS(child_status));
else if (WIFSIGNALED(child_status)) /* killed */
executor.after_failure(WTERMSIG(child_status));
else
executor.after_failure(SIGSTOP);
return true;
}
}
void test_execution::child_valgrind()
{
// We're giving up control to valgrind, so we can't
// Use anything but the cout pipe now
fmsg_pipe.close_write();
nums_pipe.close_write();
start_timeout();
exec("valgrind", "--trace-children=yes", /*"--log-fd=-1",*/ "-q", "./proxy", test.name, NULL);
}
void test_execution::child_test()
{
test.checkstream->close_read();
// Execute test
start_timeout();
string * error_msg = new unit_test::return_type(test.func(test)); // execute function
long test_time = end_timeout();
// Write failure message to pipe
fmsg_pipe << *error_msg;
fmsg_pipe.close();
// write time and valgrind flags to pipe
bool test_failed = (*error_msg != unit_test::pass_string);
delete error_msg;
delete test.checkstream;
environment.cleanup_globals();
int32_t valgrind_flags = get_valgrind_flags(test_failed);
nums_pipe << test_time;
nums_pipe << valgrind_flags;
nums_pipe.close();
}
void test_execution::after_valgrind_success(int8_t return_code)
{
fmsg_pipe.close_read();
nums_pipe.close_read();
size_t last_endl = findNthLast(test.output, '\n', 2);
if (last_endl == string::npos)
test.errormsg = "Test did not complete";
else
{
test.errormsg = test.output.substr(last_endl + 1,
test.output.length() - last_endl - 2);
if (test.errormsg == "")
test.errormsg = "Exception Thrown / Aborted";
test.valgrind_flags = return_code;
if (test.errormsg == environment.single_test_passed_string)
test.errormsg = get_valgrind_string(test.valgrind_flags);
}
}
void test_execution::after_test_success()
{
fmsg_pipe >> test.errormsg;
fmsg_pipe.close();
nums_pipe >> test.time;
nums_pipe >> test.valgrind_flags;
nums_pipe.close();
// Check for output's correctness, if that was a condition of passing
if (test.passed())
{
while (!test.checkstream->eof())
{
string checkname;
string checkstr;
*test.checkstream >> checkname;
if (test.checkstream->eof()) break;
*test.checkstream >> checkstr;
if (test.checkstream->eof()) break;
output_check check_function = (*environment.output_checks)[checkname];
if (check_function == NULL)
{
cerr << "Internal Error: in test " << test.name << ": "
<< checkname << " is not a registered OUTPUT_CHECK function" << endl;
exit(-2);
}
if (!check_function(test.output, checkstr))
test.errormsg = "Incorrect Terminal Output";
}
}
delete test.checkstream;
}
int32_t get_valgrind_flags(bool test_failed)
{
// Check for valgrind errors or leaks (if running under valgrind)
unsigned long errors = 0;
unsigned long leaked = 0;
unsigned long dubious = 0;
unsigned long reachable = 0;
unsigned long suppressed = 0;
errors = VALGRIND_COUNT_ERRORS;
VALGRIND_DO_LEAK_CHECK; //QUICK
VALGRIND_COUNT_LEAK_BLOCKS(leaked, dubious, reachable, suppressed);
return bitflags(test_failed, errors, leaked, dubious, reachable);
}
const char * get_valgrind_string(int32_t flags)
{
if (flags == 0) return unit_test::pass_string;
bool test_failed = bitflag(flags, 0);
bool errors = bitflag(flags, 1);
bool leaked = bitflag(flags, 2);
bool dubious = bitflag(flags, 3);
bool reachable = bitflag(flags, 4);
if (test_failed) return "Test failed (see output)";
if (errors) return "Invalid read/write errors";
if (leaked) return "Directly lost memory leaks";
if (dubious) return "Possibly lost memory leaks";
if (reachable) return "Still-reachable memory leaks";
return "Unknown memory errors";
}
void test_execution::start_timeout()
{
struct itimerval timeout;
timeout.it_interval.tv_sec = 0;
timeout.it_interval.tv_usec = 0;
timeout.it_value.tv_sec = test.timeout/1000;
timeout.it_value.tv_usec = (test.timeout%1000) * 1000;
EXIT_IF_ERROR(setitimer(environment.itimer_number0, &timeout, NULL));
// second real time signal in case the student calls a blocking call
timeout.it_value.tv_sec *= 10;
EXIT_IF_ERROR(setitimer(environment.itimer_number1, &timeout, NULL));
}
long test_execution::end_timeout()
{
struct itimerval timeout;
timeout.it_interval.tv_sec = 0;
timeout.it_interval.tv_usec = 0;
timeout.it_value.tv_sec = 0;
timeout.it_value.tv_usec = 0;
struct itimerval remaining;
EXIT_IF_ERROR(setitimer(environment.itimer_number0, &timeout, &remaining));
EXIT_IF_ERROR(setitimer(environment.itimer_number1, &timeout, NULL));
// There seems to be a strange -1 error here. I may just be tired,
// but I can't figure out why right now
long time = test.timeout - remaining.it_value.tv_sec*1000 - remaining.it_value.tv_usec/1000;
return (time < 0) ? 0 : time;
}
} // namespace proxy
// proxy.h
// NOTE: This is a generic file. Actual unit tests are located in
// unit_tests.cpp.
// By Jack Toole for CS 225 spring 2011
#ifndef MONAD_PROXY_H
#define MONAD_PROXY_H
#include <map>
#include <string>
#include <vector>
#include <iostream>
#include <utility>
#include "pipestream.h"
#include "monad_shared.h"
#define NO_MP_PART -1
#include "_mp_part_number.h"
#define MP_PART(x) (MP_PART_NUMBER == (x) || MP_PART_NUMBER == NO_MP_PART)
namespace proxy
{
using namespace monad_shared;
class RunTests;
typedef bool (*output_check)(const std::string &, const std::string &);
extern std::vector<unit_test> * global_tests;
typedef std::map<std::string, output_check, util::ci_less> output_check_map;
extern output_check_map * global_output_checks;
class add_unit_test
{
public:
add_unit_test(int8_t MPpart, const char * name, unit_test::function func,
int32_t points_in_part, int32_t points_in_total, long timeout,
bool is_valgrind);
private:
void lazy_init_global_tests();
void assertMPpart(int8_t MPpart, const char * name);
int32_t get_points(int32_t points_in_total, int32_t points_in_part);
};
class add_output_check
{
public:
add_output_check(const char * name, output_check func);
};
enum mode_t
{
SINGLE_TEST,
MP_PART_TESTS,
ALL_TESTS
};
struct RunTimeEnvironment
{
public:
const int itimer_number0;
const int itimer_number1;
const int timeout_signum0;
const int timeout_signum1;
const size_t max_output_length;
const char * single_test_passed_string;
std::vector<unit_test> * heap_tests;
output_check_map * output_checks;
int32_t cleanup_globals();
RunTimeEnvironment(std::vector<unit_test> *& init_tests,
output_check_map *& init_output_checks);
bool is_timeout_signal(int8_t signal_number)
{
return signal_number == timeout_signum0 ||
signal_number == timeout_signum1;
}
private:
RunTimeEnvironment(const RunTimeEnvironment & other);
RunTimeEnvironment & operator=(RunTimeEnvironment & other);
};
class RunTests
{
private:
RunTimeEnvironment & environment;
mode_t mode;
const char * test_arg;
int8_t mp_part;
public:
RunTests(int argc, char ** argv, RunTimeEnvironment & env);
int execute();
private:
void redirect_glibc_to_stderr();
void process_args(int argc, char ** argv);
protected:
int32_t execute_by_mode();
int32_t run_single_test(const char * testname);
int32_t run_single_test(unit_test & curr_test);
void handle_single_test_output(const std::string & output);
void output_single_test_passfail(const unit_test & curr_test);
int32_t run_all_tests();
int32_t get_sum_points();
int32_t get_max_testname_length();
int32_t get_max_points_length();
void output_detailed_info_if_any_failed(int32_t score);
void output_detailed_tests_info(int32_t score);
bool execute_test(unit_test & test, RunTimeEnvironment & environment, bool enable_valgrind_call);
private:
RunTests(const RunTests & other);
RunTests & operator=(const RunTests & other);
};
template <typename F>
bool fork_execute(F & executor);
class test_execution
{
private:
util::pipestream fmsg_pipe; // For error messages
util::pipestream cout_pipe; // For stdout/stderr
util::pipestream nums_pipe; // for numbers: time, valgrind
unit_test & test;
RunTimeEnvironment & environment;
bool do_valgrind;
public:
test_execution(unit_test & _test, RunTimeEnvironment & env, bool enable_valgrind_call);
void before();
void parent();
void child();
void after_success(int8_t return_code);
void after_failure(int8_t signal_number);
private:
void child_test();
void child_valgrind();
void after_test_success();
void after_valgrind_success(int8_t return_code);
void start_timeout();
long end_timeout();
private:
test_execution(const test_execution & other);
test_execution & operator=(const test_execution & other);
};
const char * get_valgrind_string(int32_t flags);
int32_t get_valgrind_flags(bool test_failed);
int32_t bitflags(unsigned long a, unsigned long b = 0, unsigned long c = 0,
unsigned long d = 0, unsigned long e = 0);
bool bitflag(int32_t flags, int32_t num);
} // namespace proxy
using std::cout;
using std::cerr;
using std::endl;
#define UNIT_TEST(MPpart,func,pointsInPart,pointsInTotal,timeout) \
proxy::unit_test::return_type \
func(proxy::unit_test & this_test); \
proxy::add_unit_test \
func##_adder(MPpart, #func, func, pointsInPart, \
pointsInTotal, timeout, false); \
proxy::unit_test::return_type \
func(proxy::unit_test & this_test)
#define VALGRIND_TEST(MPpart,func,pointsInPart,pointsInTotal,timeout) \
proxy::unit_test::return_type \
func(proxy::unit_test & this_test); \
proxy::add_unit_test \
func##_adder(MPpart, #func, func, pointsInPart, \
pointsInTotal, timeout, true); \
proxy::unit_test::return_type \
func(proxy::unit_test & this_test)
#define OUTPUT_CHECK(func) \
bool output_check_##func(const std::string & output, const std::string & expected); \
proxy::add_output_check \
output_check_##func##_adder(#func, output_check_##func); \
bool output_check_##func(const std::string & output, const std::string & expected)
#define STRINGIFY1(p) #p
#define STR(p) STRINGIFY1(p)
#define FAIL(error) return std::string(__FILE__ ":" STR(__LINE__) ": ") + (error)
#define PASS return monad_shared::unit_test::pass_string;
#define ASSERT(expr) if (!(expr)) \
FAIL("Assertion (" #expr ") failed")
#define ASSERT_OUTPUT(checkFunc, str) \
*this_test.checkstream << #checkFunc << str;
namespace proxy {
inline int32_t bitflags(unsigned long a, unsigned long b, unsigned long c,
unsigned long d, unsigned long e)
{
return ((int)(a != 0)) | (((int)(b != 0)) << 1) |
(((int)(c != 0)) << 2) | (((int)(d != 0)) << 3) |
(((int)(e != 0)) << 4) ;
}
inline bool bitflag(int32_t flags, int32_t num)
{
return (flags & (1 << num)) != 0;
}
}
#endif
"It's OK to quit a job with people you like because there are a lot of people to like out there." ~ Michael Lopp
"That's legit as shit." ~ Rick Barber
"The cheating sheet is key to success in CS 412 course." ~ CS 412 TA
"Your job is not just what you're doing; it should be preparing you for what you want to do." ~ Michael Lopp
"A reputation is a community-based opinion that you don't control. It takes years of work to develop and a single missed key responsibility to destroy." ~ Michael Lopp
"One day I will shoot you with this bullet. Can you still say that you love me?" ~ Re-l Mayer
"I didn't say I wanted to die in a blender! I said I wanted to be in a blender!" ~ Kyle Johnson
"It is practically impossible to teach good programming to students that have had a prior exposure to BASIC: as potential programmers they are mentally mutilated beyond hope of regeneration." ~ Edsger Dijkstra
"Always code as if the guy who ends up maintaining your code will be a violent psychopath who knows where you live. Code for readability." ~ John Woods
"The primary purpose of the DATA statement is to give names to constants; instead of referring to pi as 3.141592653589793 at every appearance, the variable PI can be given that value with a DATA statement and used instead of the longer form of the constant. This also simplifies modifying the program, should the value of pi change." ~ FORTRAN manual for Xerox computers
"Any sufficiently advanced bug is indistinguishable from a feature." ~ Rich Kulawiec
"Worry loudly now or scream louder later." ~ Michael Lopp
"I can always tell when I don't want to do something because... I never do it." ~ Michael Lopp
"If you think you can do it all, you're thinking too small." ~ Michael Lopp
"When you say 'You suck', I think 'I win'." ~ Michael Lopp
"Honestly, if you don't fit in, then you're probably doing the right thing." ~ LIGHTS
"It should be noted that no ethically-trained software engineer would ever consent to write a DestroyBaghdad procedure. Basic professional ethics would instead require him to write a DestroyCity procedure, to which Baghdad could be given as a parameter." ~ Nathaniel Borenstein
"Simplicity and elegance are unpopular because thy require hard work and discipline to achieve and education to be appreciated." ~ Edsger Dijkstra
"I love deadlines. I like the whooshing sound they make as they fly by." ~ Douglas Adams
"Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it." ~ Brian Kernighan
"The first 90% of the code accounts for the first 90% of the development time. The remaining 10% of the code accounts for the other 90% of the development time." ~ Tom Cargill
"It always takes longer than you expect, even when you take into account Hofstadter's Law." ~ Hofstadter's Law
"The most likely way for the world to be destroyed, most experts agree, is by accident. That's where we come in; we're computer professionals. We cause accidents." ~ Nathaniel Borenstein
"Perfection is achieved, not when there is nothing more to add, but when there is nothing left to take away." ~ Antoine de Saint-Exupery
#!/bin/bash
testcases="mp1 lab02 lab03 lab04 lab06 lab07 lab10 lab12"
#make
for assign in $testcases
do
./monad $assign --newtests --solution --noupdate --staff &>/dev/null
score=$?
if [ $score -lt 100 ] || [ $score -eq 255 ]
then
echo "FAILED: Testcase $assign failed with score of $score"
else
echo "passed: $assign: $score"
fi
done
../util/util.cpp
\ No newline at end of file
../util/util.h
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment