Skip to content

Commit fd4cb05

Browse files
committed
Update lab02 for 2025
1 parent 18c6e39 commit fd4cb05

File tree

4 files changed

+108
-121
lines changed

4 files changed

+108
-121
lines changed

.github/workflows/classroom.yml

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
name: Autograding Tests
2+
on:
3+
- push
4+
- workflow_dispatch
5+
- repository_dispatch
6+
permissions:
7+
checks: write
8+
actions: read
9+
contents: read
10+
jobs:
11+
run-autograding-tests:
12+
runs-on: ubuntu-22.04
13+
if: github.actor != 'github-classroom[bot]'
14+
steps:
15+
- name: Checkout repository
16+
uses: actions/checkout@v3
17+
18+
- name: Set up Python
19+
uses: actions/setup-python@v4
20+
with:
21+
python-version: 3.7
22+
23+
- name: Install pycparser
24+
run: pip install tabulate psutil
25+
26+
- name: Run tests
27+
run: ./check

grading/Makefile renamed to Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,4 @@ clean:
1414

1515
autograder-clean:
1616
rm -rf test_ll_equal.c test_ll_cycle.c grading/__pycache__
17+

check

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
cp grading/Makefile .
2-
cp grading/*.c .
31
make clean
2+
make autograder-clean
3+
cp grading/*.c .
44

55
{
66
title() {

grading/utils.py

Lines changed: 78 additions & 119 deletions
Original file line numberDiff line numberDiff line change
@@ -1,50 +1,45 @@
11
import os
22
import re
33
import json
4-
import boto3
54
import base64
65
import shutil
76
import hashlib
87
import zipfile
9-
import paramiko
108
import tempfile
119
import pycparser
1210
from os import environ
1311
from glob import glob
14-
from Crypto import Random
1512
from subprocess import run
1613
from subprocess import PIPE
1714
from tabulate import tabulate
18-
from Crypto.Cipher import AES
1915
from distutils.dir_util import copy_tree
2016

2117

22-
# encrypt a string
23-
def encrypt(raw):
24-
def pad(s):
25-
return s + (16 - len(s) % 16) * chr(16 - len(s) % 16)
26-
rawkey = environ['AUTOGRADERS_KEY']
27-
key = hashlib.sha256(rawkey.encode()).digest()
28-
raw = pad(raw)
29-
iv = Random.new().read(AES.block_size)
30-
cipher = AES.new(key, AES.MODE_CBC, iv)
31-
return base64.b64encode(iv + cipher.encrypt(raw)).decode()
32-
33-
34-
# decrypt an encrypted string
35-
def decrypt(enc):
36-
def unpad(s):
37-
if (type(s[-1]) == int):
38-
return s[0: -s[-1]]
39-
return s[0: -ord(s[-1])]
40-
enc = base64.b64decode(enc)
41-
iv = enc[:16]
42-
rawkey = environ['AUTOGRADERS_KEY']
43-
key = hashlib.sha256(rawkey.encode()).digest()
44-
cipher = AES.new(key, AES.MODE_CBC, iv)
45-
return unpad(cipher.decrypt(enc[16:])).decode()
18+
# builds program file string
19+
def get_ex(ch, ex):
20+
return f"./ch{ch}/ex-{ch}.{ex}.c"
4621

4722

23+
# builds program test file string
24+
def get_in(ch, ex):
25+
if (ch == 1):
26+
if (ex == 9):
27+
return "./ch1/spaces.test"
28+
elif (ex == 13 or ex == 14):
29+
return "./ch1/words.test"
30+
elif (ex == 17):
31+
return "./ch1/long.test"
32+
elif (ex == 20):
33+
return "./ch1/tabs.test"
34+
else:
35+
return "./ch1/input.test"
36+
else:
37+
return "placeholder.test"
38+
39+
# builds expected output file string
40+
def get_out(ch, ex):
41+
return f"./ch{ch}/expected-{ch}.{ex}"
42+
4843
# reads a file
4944
def read(filename):
5045
f = open(filename, 'r')
@@ -154,12 +149,66 @@ def expected_files(files, dir='.'):
154149
def execute(cmd=[], shell=False, dir='.', input=None, encoding='ascii', timeout=5):
155150
return run(cmd, shell=shell, stdout=PIPE, stderr=PIPE, input=input, cwd=dir, timeout=timeout)
156151

157-
158152
# makes a target
159153
def make(target=''):
160154
return execute(cmd=['make', target])
161155

162156

157+
# compile a target
158+
def compile(target=''):
159+
return execute(cmd=['gcc', '-std=c99', target])
160+
161+
# run a file
162+
def run_program(command):
163+
return run(command, shell=True, stdout=PIPE, stderr=PIPE, cwd='.', timeout=10)
164+
165+
166+
# passed message
167+
def passed(*args):
168+
if len(args) > 0:
169+
return 'passed: ' + args[0]
170+
return 'passed'
171+
172+
173+
# failed message
174+
def failed(*args):
175+
if len(args) > 0:
176+
return 'failed: ' + args[0]
177+
return 'failed'
178+
179+
180+
# incomplete message
181+
def incomplete(*args):
182+
if len(args) > 0:
183+
return 'incomplete: ' + args[0]
184+
return 'incomplete'
185+
186+
187+
# creates a compilation error msg
188+
def create_error(filename, msg):
189+
if msg != '':
190+
return '[%s]\n\n%s\n' % (filename, msg)
191+
return ''
192+
193+
194+
# creates a pretty result report
195+
def report(table):
196+
return tabulate(table, headers=['Exercise', 'Grade', 'Message'])
197+
198+
199+
# writes autograder result (lab)
200+
def write_result(grade, msg):
201+
write_json({'grade': grade, 'output': msg}, 'output.json')
202+
203+
204+
# finds a specific function in the ast
205+
def find_func(ast, name):
206+
for f in ast.ext:
207+
if type(f) == pycparser.c_ast.FuncDef and f.decl.name == name:
208+
return f
209+
return None
210+
211+
163212
# parses a form
164213
def parse_form(f):
165214
f = open(f, 'r', encoding='latin1')
@@ -212,93 +261,3 @@ def parse_c_raw(filename):
212261
text = task.stdout.decode().strip()
213262
parser = pycparser.c_parser.CParser()
214263
return parser.parse(text)
215-
216-
217-
# passed message
218-
def passed(*args):
219-
if len(args) > 0:
220-
return 'passed: ' + args[0]
221-
return 'passed'
222-
223-
224-
# failed message
225-
def failed(*args):
226-
if len(args) > 0:
227-
return 'failed: ' + args[0]
228-
return 'failed'
229-
230-
231-
# incomplete message
232-
def incomplete(*args):
233-
if len(args) > 0:
234-
return 'incomplete: ' + args[0]
235-
return 'incomplete'
236-
237-
238-
# creates a compilation error msg
239-
def create_error(filename, msg):
240-
if msg != '':
241-
return '[%s]\n\n%s\n' % (filename, msg)
242-
return ''
243-
244-
245-
# creates a pretty result report
246-
def report(table):
247-
return tabulate(table, headers=['Exercise', 'Grade', 'Message'])
248-
249-
250-
# writes autograder result
251-
def write_result(grade, msg):
252-
write_json({'grade': grade, 'output': msg}, 'output.json')
253-
254-
255-
# finds a specific function in the ast
256-
def find_func(ast, name):
257-
for f in ast.ext:
258-
if type(f) == pycparser.c_ast.FuncDef and f.decl.name == name:
259-
return f
260-
return None
261-
262-
263-
class AWSTask:
264-
265-
def __init__(self, name, instance='c5.2xlarge', AMI='ami-0e262d4de9c0b73fd', key='cc3'):
266-
ec2 = boto3.resource('ec2')
267-
self.instance = ec2.create_instances(
268-
ImageId=AMI,
269-
MaxCount=1,
270-
MinCount=1,
271-
InstanceType=instance,
272-
SecurityGroupIds=['sg-00b6ec171be0d43f7'],
273-
KeyName=key,
274-
TagSpecifications=[
275-
{
276-
'ResourceType': 'instance',
277-
'Tags': [
278-
{
279-
'Key': 'Name',
280-
'Value': name
281-
},
282-
]
283-
},
284-
],
285-
)[0]
286-
self.instance.wait_until_running()
287-
self.instance.reload()
288-
self.instance.wait_until_running()
289-
self.key = key
290-
self.ipv4 = self.instance.public_ip_address
291-
292-
def connect(self):
293-
key = paramiko.RSAKey.from_private_key_file(self.key + '.pem')
294-
client = paramiko.SSHClient()
295-
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
296-
client.connect(hostname=self.ipv4, username='ubuntu', pkey=key)
297-
self.client = client
298-
299-
def run(self, cmd, timeout=30):
300-
stdin, stdout, stderr = self.client.exec_command(cmd, timeout=timeout)
301-
return (stdout.read().decode(), stderr.read().decode())
302-
303-
def terminate(self):
304-
self.instance.terminate()

0 commit comments

Comments
 (0)