-
Notifications
You must be signed in to change notification settings - Fork 155
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
9 changed files
with
278 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
# Gradescope c# Autograder Example | ||
|
||
[View project source on Github](https://github.com/gradescope/autograder_samples/tree/master/c#/src) - [autograder.zip](https://github.com/gradescope/autograder_samples/raw/master/c#/src/autograder.zip) - [sample solution](https://github.com/gradescope/autograder_samples/raw/master/python/src/solution/calculator.py) | ||
|
||
## Project Description | ||
|
||
This project is a simple example of how to build a C# autograder. | ||
|
||
|
||
|
||
## Dependencies (for tests) | ||
|
||
- Python 3+ | ||
- NUnit | ||
|
||
## Example Test | ||
|
||
``` | ||
[TestFixture] | ||
public class HelloWorldTest | ||
{ | ||
[Test, Property("Weight", 1.0), Property("Visibility", "visible")] | ||
public void HelloTest() | ||
{ | ||
Assert.AreEqual(HelloWorld.Hello(), "Hello"); | ||
} | ||
[Test, Property("Weight", 2.0), Property("Visibility", "hidden"), Property("Name", "Bye")] | ||
public void MyTest2() | ||
{ | ||
Assert.AreEqual(HelloWorld.Bye(), "Bye"); | ||
} | ||
} | ||
``` | ||
|
||
## Running Tests | ||
|
||
``` | ||
mcs -target:library -pkg:nunit -out:test.dll test.cs HelloWorld.cs | ||
nunit-console test.dll | ||
``` | ||
|
||
# Files | ||
|
||
## [setup.sh](src/setup.sh) | ||
|
||
This script installs NUnit and the Mono development tools. | ||
|
||
## [run_autograder](src/run_autograder) | ||
|
||
This script copies the student's submission to the target directory, | ||
compiles the required files (the test case and the student's submission), | ||
executes the test suite, and then converts the XML test results to Gradescope's | ||
JSON format. | ||
|
||
## [nunit_to_gs.py](src/nunit_to_gs.py) | ||
|
||
This python script loads the test results from TestResults.xml | ||
and converts them into Gradescope's JSON format. This script is what | ||
reads the property tags that you supplied in the test case, as shown above, | ||
and turns those into the appropriate Gradescope metadata. | ||
|
||
## [Framework.cs](src/Framework.cs) | ||
|
||
This is a blank template file for the students to fill in. Note that | ||
their solution must be called HelloWorld.cs for the autograder to work | ||
correctly. | ||
|
||
## [autograder.zip](src/autograder.zip) | ||
|
||
This is a zipped up autograder that can be directly uploaded to Gradescope. | ||
You can then try out the correct and/or incorrect solutions we provide to see how | ||
the autograder works. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
using System; | ||
|
||
public class HelloWorld | ||
{ | ||
public static string Hello() | ||
{ | ||
return ""; | ||
} | ||
|
||
public static string Bye() | ||
{ | ||
return ""; | ||
} | ||
} |
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
using System; | ||
|
||
public class HelloWorld | ||
{ | ||
public static string Hello() | ||
{ | ||
Console.WriteLine("Output"); | ||
Console.Error.WriteLine("Error"); | ||
return "Hello"; | ||
} | ||
|
||
public static string Bye() | ||
{ | ||
return "Hello"; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,130 @@ | ||
import os | ||
import json | ||
import xml.etree.ElementTree as et | ||
|
||
class NUnitTestCase(object): | ||
def __init__(self, node, output_map): | ||
self.node = node | ||
self.output_map = output_map | ||
self.load_properties() | ||
|
||
def load_properties(self): | ||
self.properties = {} | ||
for property_node in self.node.find('properties').getchildren(): | ||
key = property_node.get('name') | ||
value = property_node.get('value') | ||
self.properties[key] = value | ||
|
||
def property(self, key): | ||
return self.properties.get(key, None) | ||
|
||
def max_score(self): | ||
if self.property('Weight'): | ||
return float(self.property('Weight')) | ||
else: | ||
return 1.0 | ||
|
||
def score(self): | ||
if self.property('Score'): | ||
return float(self.property('Score')) | ||
else: | ||
if self.node.get('success') == 'True': | ||
return self.max_score() | ||
else: | ||
return 0.0 | ||
|
||
def failure(self): | ||
failure_node = self.node.find('failure') | ||
if failure_node is not None: | ||
message_node = failure_node.find('message') | ||
return message_node.text | ||
|
||
|
||
def visibility(self): | ||
if self.property('Visibility'): | ||
return self.property('Visibility') | ||
|
||
def name(self): | ||
if self.property('Name'): | ||
return self.property('Name') | ||
else: | ||
return self.node.get('name') | ||
|
||
def output(self): | ||
if self.node.get('success') != 'True': | ||
return self.failure() | ||
else: | ||
key = self.node.get('name') | ||
if key in self.output_map: | ||
return self.output_map[key] | ||
|
||
def as_dict(self): | ||
result = { | ||
"score": self.score(), | ||
"max_score": self.max_score(), | ||
"name": self.name() | ||
} | ||
if self.visibility(): | ||
result["visibility"] = self.visibility() | ||
if self.output(): | ||
result["output"] = self.output() | ||
return result | ||
|
||
|
||
class NUnitResultsLoader(object): | ||
|
||
def __init__(self): | ||
self.results = {} | ||
self.results['stdout_visibility'] = 'hidden' | ||
self.results['visibility'] = 'visible' | ||
self.results['tests'] = [] | ||
self.output_map = {} | ||
self.load_stdout_stderr() | ||
|
||
def load_stdout_stderr(self): | ||
if os.path.exists('stdout_and_stderr'): | ||
with open('stdout_and_stderr', 'r') as f: | ||
data = f.readlines() | ||
self.process_stdout_stderr(data) | ||
|
||
def process_stdout_stderr(self, data): | ||
active = False | ||
current_test = None | ||
current_output = "" | ||
for line in data: | ||
if line.startswith('Tests run:'): | ||
self.output_map[current_test] = self.output_map[current_test][0:-1] | ||
break | ||
if line.startswith('***** '): | ||
current_test = line[6:-1] | ||
active = True | ||
self.output_map[current_test] = '' | ||
elif active: | ||
self.output_map[current_test] += line | ||
|
||
|
||
def process_results_file(self, filename): | ||
root = et.parse(filename).getroot() | ||
self.process_library_suite(root.find('test-suite')) | ||
|
||
def process_library_suite(self, suite_node): | ||
self.results['execution_time'] = float(suite_node.get('time')) | ||
for child in suite_node.find('results').getchildren(): | ||
self.process_file_suite(child) | ||
|
||
def process_file_suite(self, suite_node): | ||
for child in suite_node.find('results').getchildren(): | ||
self.process_test_case(child) | ||
|
||
def process_test_case(self, test_case_node): | ||
test_case = NUnitTestCase(test_case_node, self.output_map) | ||
self.results['tests'].append(test_case.as_dict()) | ||
|
||
def print_json(self): | ||
print(json.dumps(self.results)) | ||
|
||
|
||
loader = NUnitResultsLoader() | ||
loader.process_results_file('TestResult.xml') | ||
loader.print_json() | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
#! /bin/bash | ||
|
||
cd /autograder/source | ||
cp /autograder/submission/HelloWorld.cs . | ||
mcs -target:library -pkg:nunit -out:test.dll test.cs HelloWorld.cs | ||
nunit-console test.dll -nodots -labels &> stdout_and_stderr | ||
python3 nunit_to_gs.py > /autograder/results/results.json |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
#! /bin./bash | ||
|
||
apt-get update | ||
apt-get install -qy nunit nunit-console monodevelop |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
using System; | ||
|
||
public class HelloWorld | ||
{ | ||
public static string Hello() | ||
{ | ||
return "Hello"; | ||
} | ||
|
||
public static string Bye() | ||
{ | ||
return "Bye"; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
using System; | ||
using System.Text; | ||
using System.Collections.Generic; | ||
using NUnit.Framework; | ||
|
||
[TestFixture] | ||
public class HelloWorldTest | ||
{ | ||
[Test, Property("Weight", 1.0), Property("Visibility", "visible")] | ||
public void HelloTest() | ||
{ | ||
Assert.AreEqual(HelloWorld.Hello(), "Hello"); | ||
} | ||
|
||
[Test, Property("Weight", 2.0), Property("Visibility", "hidden"), Property("Name", "Bye")] | ||
public void MyTest2() | ||
{ | ||
Assert.AreEqual(HelloWorld.Bye(), "Bye"); | ||
} | ||
} |