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

jack

git-svn-id: https://subversion.cs.illinois.edu/svn/cs225@3364 6fbd10e7-183d-0410-a318-cb416676e4f2
parents
No related branches found
No related tags found
No related merge requests found
Makefile 0 → 100644
CC = g++
CFLAGS = -Wall
EXENAME = monad
OBJS = $(EXENAME).o util.o output.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 $@
.cpp.o: $(wildcard *.h)
$(CC) $(CFLAGS) -c $(@:.o=.cpp) -o $@
.PHONY: clean
clean:
rm -f *.o $(EXENAME) $(wildcard $(EXENAME)$(EXENAME)*) $(IDFILE)
.PHONY: clean.sh
clean.sh: $(EXENAME)
./clean.sh
CC = g++
CFLAGS += -Wall
TESTEXE = runtests
TESTOBJS += $(TESTEXE).o util.o unit_tests.o output.o
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
TODO.txt 0 → 100644
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 testsuite/ 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 on student idiocy
x on crazy memory errors, glibc issues, valgrind aborts
x fix "" failure messages
if compilation fails
STYLE
Directory structure for config.ini includes
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
replace ln -s util/* system() calls
man isatty
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
// blindspot
// 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 blindspot
{
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
}
}
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 blindspot;
// Find blindspot/ 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 << "CS 225 Conceptual Blindspot" << 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 blindspot::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 blindspot::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("./.blindspotid"))
{
cerr << "Could not find blindspot directory. "
"Please run ./blindspot from the directory it is located in."
<< endl;
exit(-1);
}
}
void blindspot::printHelp()
{
cout << "Usage: blindspot 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
<< "blindspot settings and individual test settings are stored in config.ini files" << endl
<< "The following options are available for blindspot's ./config.ini:" << endl
<< "[SVN Root]" << endl
<< "; svn base url here (http://.../cs225/)" << endl
<< "[Blindspot Files]" << endl
<< "; Any files to be copied from blindspot ./ 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 blindspot::printInfo()
{
cout << "CS 225 Conceptual Blindspot" << endl
<< "Version 1.0 : 2 February 2011" << endl
<< "Developed by Jack Toole Spring 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 blindspot::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["Blindspot 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 blindspot::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 blindspot::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 blindspot::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 blindspot::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 blindspot or command line command
void blindspot::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());
}
clean.sh 0 → 100755
#!/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.runtests
memcheck.h
output.cpp
output.h
runtests.cpp
runtests.h
unit_test.h
valgrind.h
[Libraries]
util
/*
----------------------------------------------------------------
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
#include <iostream>
#include "output.h"
#include "util.h"
using namespace runtests;
using namespace util;
using std::cout;
using std::cerr;
using std::endl;
namespace output
{
void header(const string & title)
{
cout << title << "..." << 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 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 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 (error == PASS_STRING)
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 testoutput
output.h 0 → 100644
#ifndef OUTPUT_H
#define OUTPUT_H
#include <string>
#include "unit_test.h"
namespace output
{
void header(const std::string & title);
void total_score(int32_t score);
void testname(const runtests::unit_test & curr_test, int32_t max_testname_len, int32_t max_points_len);
void warning(const std::string & message);
void detailed_info(const runtests::unit_test & curr_test);
} // namespace output
#endif
../util/pipestream.cpp
\ No newline at end of file
../util/pipestream.h
\ No newline at end of file
#!/bin/bash
testcases="mp1 lab02 lab03 lab04 lab06 lab07 lab10 lab12"
#make
for assign in $testcases
do
./monad $assign --newtests --solution --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
// runtests.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 "output.h"
#include "pipestream.h"
#include "runtests.h"
#include "util.h"
#include "valgrind.h"
using std::string;
using std::vector;
using std::pair;
using namespace util;
using namespace output;
namespace runtests
{
vector<unit_test> * global_tests = NULL;
output_check_map * global_output_checks = NULL;
const char * unit_test::pass_string = "~~PASSED~~";
}
OUTPUT_CHECK(equals)
{
return output == expected;
}
OUTPUT_CHECK(contains)
{
return output.find(expected) != string::npos;
}
int main(int argc, char ** argv)
{
using namespace runtests;
// 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 runtests {
int8_t RunTimeEnvironment::singleton = 0;
// 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
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 = UnitTestContainer(curr_test, environment).execute(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 (error == PASS_STRING)
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 = UnitTestContainer(curr_test, environment).execute(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;
}
// originally from stackoverflow.com user plinth
// http://stackoverflow.com/questions/2180079/how-can-i-copy-a-file-on-unix-using-c
// Modified by Jack Toole
bool UnitTestContainer::execute(bool enable_valgrind_call)
{
cout << std::flush;
bool do_valgrind = enable_valgrind_call && test.is_valgrind;
// Make our pipes
pipestream fmsg_pipe; // For error messages
pipestream cout_pipe; // For stdout/stderr
pipestream nums_pipe; // for numbers: time, valgrind
if (!do_valgrind)
test.checkstream = new pipestream;
// Fork
pid_t pid;
pid = fork();
if (pid == 0) /* 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)
{
// 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();
child_valgrind();
}
else // if (!test.is_valgrind)
{
child_test(fmsg_pipe, nums_pipe);
}
// Unfortunately necessary to use a return stack instead of
// exit() to get rid of valgrind errors
// (which is important if we use valgrind ./runtests recursively)
return false; // previously exit(0);
}
else if (pid < 0)
{
/* error - couldn't start process - you decide how to handle */
perror("Abort: " __FILE__ ":" STR(__LINE__));
exit(-1);
}
else
{
/* parent - wait for child - this has all error handling, you
* could just call wait() as long as you are only expecting to
* have one child process at a time.
*/
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();
// Eat child process
// Should be instant because of cout_pipe EOF
int child_status;
pid_t ws = waitpid( pid, &child_status, 0); //should return immediately
EXIT_IF_ERROR(ws == -1);
if (WIFEXITED(child_status)) /* exit code in child_status */
{
int8_t return_code = WEXITSTATUS(child_status);
if (do_valgrind)
{
fmsg_pipe.close_read();
nums_pipe.close_read();
valgrind_test_exit(return_code);
}
else // if (!test.is_valgrind)
{
test_exit(fmsg_pipe, nums_pipe);
}
return true;
}
else if (WIFSIGNALED(child_status)) /* killed */
{
fmsg_pipe.close_read();
nums_pipe.close_read();
test_signaled(WTERMSIG(child_status));
return true;
}
else
{
fmsg_pipe.close_read();
nums_pipe.close_read();
test.errormsg = "Unexpectedly Aborted";
return true;
}
}
}
void UnitTestContainer::child_valgrind()
{
start_timeout();
execl("/usr/bin/valgrind", "/usr/bin/valgrind", "--trace-children=yes", /*"--log-fd=-1",*/ "-q", "./runtests", test.name, NULL);
// Execl failed
cerr << "valgrind execute failed" << endl;
exit(-1);
}
void UnitTestContainer::child_test(pipestream & fmsg_pipe, pipestream & nums_pipe)
{
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 != 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 UnitTestContainer::valgrind_test_exit(int8_t return_code)
{
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 UnitTestContainer::test_exit(pipestream & fmsg_pipe, pipestream & nums_pipe)
{
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.errormsg == PASS_STRING)
{
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;
}
void UnitTestContainer::test_signaled(int signum)
{
if (signum == environment.timeout_signum0 || signum == environment.timeout_signum1)
{
test.errormsg = string("Timed out") + " (" + to_string(test.timeout) + "ms)";
test.time = test.timeout;
}
else
test.errormsg = strsignal(signum);
}
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 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 UnitTestContainer::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 UnitTestContainer::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;
}
void RunTests::printInfo()
{
cout << "CS 225 RunTests" << endl
<< "Version 1.0 : 2 February 2011" << endl
<< "Developed by Jack Toole Spring 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;
}
UnitTestContainer::UnitTestContainer(unit_test & _test, RunTimeEnvironment & env)
: environment(env), test(_test) { }
} // namespace runtests
// runtests.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 RUNTESTS_H
#define RUNTESTS_H
#include <map>
#include <string>
#include <vector>
#include <iostream>
#include <utility>
#include "pipestream.h"
#include "unit_test.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 runtests
{
class RunTests;
struct unit_test;
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);
private:
RunTimeEnvironment(const RunTimeEnvironment & other);
RunTimeEnvironment & operator=(RunTimeEnvironment & other);
static int8_t singleton;
};
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);
void printInfo();
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);
private:
RunTests(const RunTests & other);
RunTests & operator=(const RunTests & other);
};
class UnitTestContainer
{
private:
RunTimeEnvironment & environment;
unit_test & test;
public:
UnitTestContainer(unit_test & _test, RunTimeEnvironment & env);
bool execute(bool enable_valgrind_call);
private:
void child_valgrind();
void child_test(util::pipestream & fmsg_pipe, util::pipestream & nums_pipe);
void valgrind_test_exit(int8_t return_code);
void test_exit(util::pipestream & fmsg_pipe, util::pipestream & nums_pipe);
void test_signaled(int signum);
void start_timeout();
long end_timeout();
private:
UnitTestContainer(const UnitTestContainer & other);
UnitTestContainer & operator=(const UnitTestContainer & 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 runtests
using std::cout;
using std::cerr;
using std::endl;
#define UNIT_TEST(MPpart,func,pointsInPart,pointsInTotal,timeout) \
runtests::unit_test::return_type \
func(runtests::unit_test & this_test); \
runtests::add_unit_test \
func##_adder(MPpart, #func, func, pointsInPart, \
pointsInTotal, timeout, false); \
runtests::unit_test::return_type \
func(runtests::unit_test & this_test)
#define VALGRIND_TEST(MPpart,func,pointsInPart,pointsInTotal,timeout) \
runtests::unit_test::return_type \
func(runtests::unit_test & this_test); \
runtests::add_unit_test \
func##_adder(MPpart, #func, func, pointsInPart, \
pointsInTotal, timeout, true); \
runtests::unit_test::return_type \
func(runtests::unit_test & this_test)
#define OUTPUT_CHECK(func) \
bool output_check_##func(const std::string & output, const std::string & expected); \
runtests::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 PASS_STRING
#define ASSERT(expr) if (!(expr)) \
FAIL("Assertion (" #expr ") failed")
#define ASSERT_OUTPUT(checkFunc, str) \
*this_test.checkstream << #checkFunc << str;
namespace runtests {
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
#ifndef UNIT_TEST_H
#define UNIT_TEST_H
#include <string>
#include "pipestream.h"
namespace runtests
{
class RunTests;
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 / runtests 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;
}
};
}
#define PASS_STRING "~~PASSED~~"
#endif
../util/util.cpp
\ No newline at end of file
util.h 0 → 120000
../util/util.h
\ No newline at end of file
Source diff could not be displayed: it is too large. Options to address this: view the blob.
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