Skip to content

Commit

Permalink
C# example
Browse files Browse the repository at this point in the history
  • Loading branch information
arjun810 committed Aug 10, 2018
1 parent ccb9b69 commit c8dab6d
Show file tree
Hide file tree
Showing 9 changed files with 278 additions and 0 deletions.
73 changes: 73 additions & 0 deletions c#/README.md
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.
14 changes: 14 additions & 0 deletions c#/src/Framework.cs
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 added c#/src/autograder.zip
Binary file not shown.
16 changes: 16 additions & 0 deletions c#/src/incorrect/HelloWorld.cs
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";
}
}
130 changes: 130 additions & 0 deletions c#/src/nunit_to_gs.py
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()

7 changes: 7 additions & 0 deletions c#/src/run_autograder
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
4 changes: 4 additions & 0 deletions c#/src/setup.sh
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
14 changes: 14 additions & 0 deletions c#/src/solution/HelloWorld.cs
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";
}
}
20 changes: 20 additions & 0 deletions c#/src/test.cs
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");
}
}

0 comments on commit c8dab6d

Please sign in to comment.