Skip to content

Commit

Permalink
Compare submissions to model answer and other submissions
Browse files Browse the repository at this point in the history
  • Loading branch information
Mikael-Lenander committed May 28, 2024
1 parent 1604329 commit 9a22d5f
Show file tree
Hide file tree
Showing 21 changed files with 9,421 additions and 15 deletions.
8,941 changes: 8,936 additions & 5 deletions assets/css/main.css

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion assets/css/main.css.map

Large diffs are not rendered by default.

40 changes: 38 additions & 2 deletions assets/js/aplus.js
Original file line number Diff line number Diff line change
Expand Up @@ -317,7 +317,7 @@ $(function () {
let fileExtOrAlias = "";
const splitUrl = url.split('.');
if (splitUrl.length > 1) { // Has file extension
fileExtOrAlias = splitUrl.pop();
fileExtOrAlias = splitUrl.pop().split('?')[0];
} else if (splitUrl.pop().endsWith("Dockerfile")) {
fileExtOrAlias = "dockerfile";
}
Expand All @@ -333,19 +333,55 @@ $(function () {
const table = $('<table/>').addClass('src');

if (!options || !options.noHighlight) {
const textLines = pre.text().split(/\r\n|\r|\n/g);
hljs.highlightElement(codeBlock[0]);

// Add line numbers
const lines = pre.html().split(/\r\n|\r|\n/g);
const maxLinesToShow = Math.min(lines.length, 5000);
let currentLinesToShow = maxLinesToShow; // Initial number of lines to show
let testLineNumber = 0;
const filename = pre.data('filename');

const testId = () => {
testLineNumber++;
return `data-testid="${filename}-line-${testLineNumber}"`;
}

const getNormalRow = (lineNumber, lineContent) => {
return `<td class="num unselectable">${lineNumber}</td><td class="src" ${testId()}>${lineContent}</td>`;
}
let comparedLineNumber = 0, originalLineNumber = 0;
const getDiffRow = (lineNumber, lineContent, textContent) => {
const diffCode = textContent.slice(0, 2);
let backgroundColorClass;
if (diffCode === '+ ') {
originalLineNumber++;
backgroundColorClass = 'new';
} else if (diffCode === '- ') {
comparedLineNumber++;
backgroundColorClass = 'old';
} else {
comparedLineNumber++;
originalLineNumber++;
backgroundColorClass = '';
}
const showComparedLineNumber = (diffCode === ' ' || diffCode === '- ') ? comparedLineNumber : ''
const showOriginalLineNumber = (diffCode === ' ' || diffCode === '+ ') ? originalLineNumber : ''
return `
<td class="num unselectable ${backgroundColorClass}">${showComparedLineNumber}</td>
<td class="num unselectable ${backgroundColorClass}">${showOriginalLineNumber}</td>
<td class="src ${backgroundColorClass}" ${testId()}>${lineContent}</td>
`
}
const getRow = options.compareMode ? getDiffRow : getNormalRow;

const getLines = (start, end) => {
const fragment = document.createDocumentFragment();

for (let i = start; i <= end; i++) {
const row = $('<tr>').append(
'<td class="num unselectable">' + i + '</td><td class="src">' + lines[i - 1] + '</td>'
getRow(i, lines[i - 1], textLines[i - 1])
);
fragment.appendChild(row[0]);
}
Expand Down
6 changes: 6 additions & 0 deletions assets/sass/components/_highlight.scss
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,9 @@ table.src tr td.src {
table.src.no-wrap tr td.src {
white-space: pre;
}
table.src tr td.old {
background: #f8d7da;
}
table.src tr td.new {
background: #d4edda;
}
37 changes: 37 additions & 0 deletions e2e_tests/assets/wallet.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
class Wallet:

# Added line in wallet.py.
def __init__(self, owner_name, balance=0):
self.__owner_name = owner_name


def get_owner_name(self):
return self.__owner_name


def get_balance(self):
return self.__balance


def deposit(self, amount):
if amount > 0:
self.__balance += amount
return True
return False


def withdraw(self, amount):
if amount > 0 and self.get_balance() >= amount:
self.__balance -= amount
return True
return False


def has_more_money(self, other):
if self.get_balance() > other.get_balance():
return True
return False


def __str__(self):
return "{:s}: {:.2f} euros".format(self.get_owner_name(), self.get_balance())
37 changes: 37 additions & 0 deletions e2e_tests/assets/wallet2.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
class Wallet:

# Added line in wallet.py2.
def __init__(self, owner_name, balance=0):
self.__owner_name = owner_name


def get_owner_name(self):
return self.__owner_name


def get_balance(self):
return self.__balance


def deposit(self, amount):
if amount > 0:
self.__balance += amount
return True
return False


def withdraw(self, amount):
if amount > 0 and self.get_balance() >= amount:
self.__balance -= amount
return True
return False


def has_more_money(self, other):
if self.get_balance() > other.get_balance():
return True
return False


def __str__(self):
return "{:s}: {:.2f} euros".format(self.get_owner_name(), self.get_balance())
48 changes: 48 additions & 0 deletions e2e_tests/assets/wallet_program.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# Added line in wallet_program.py
from wallet import Wallet


def main():
name1 = input("Who is the owner of the first wallet?\n")
balance1 = float(input("What is the balance of the first wallet?\n"))
name2 = input("Who is the owner of the second wallet?\n")
balance2 = float(input("What is the balance of the second wallet?\n")) # Modified line in wallet_program.py.
print("Creating wallets...")
wallet1 = Wallet(name1, balance1)
wallet2 = Wallet(name2, balance2)
print()
print("Wallets created:")
print(wallet1)
print(wallet2)
print()
amount_deposit1 = float(input("How much is deposited to the first wallet?\n"))
if wallet1.deposit(amount_deposit1):
print("Deposit successful!")
else:
print("Deposit failed!")
print("Balance of first wallet: {:.2f}".format(wallet1.get_balance()))
amount_deposit2 = float(input("How much is deposited to the second wallet?\n"))
if wallet2.deposit(amount_deposit2):
print("Deposit successful!")
else:
print("Deposit failed!")
print("Balance of second wallet: {:.2f}".format(wallet2.get_balance()))
amount_withdraw1 = float(input("How much is withdrawn from the first wallet?\n"))
if wallet1.withdraw(amount_withdraw1):
print("Withdraw successful!")
else:
print("Withdraw failed!")
print("Balance of first wallet: {:.2f}".format(wallet1.get_balance()))
amount_withdraw2 = float(input("How much is withdrawn from the second wallet?\n"))
if wallet2.withdraw(amount_withdraw2):
print("Withdraw successful!")
else:
print("Withdraw failed!")
print("Balance of second wallet: {:.2f}".format(wallet2.get_balance()))
if wallet1.has_more_money(wallet2):
print("The wallet of {:s} has more money than the wallet of {:s}.".format(wallet1.get_owner_name(), wallet2.get_owner_name()))
else:
print("The wallet of {:s} does not have more money than the wallet of {:s}.".format(wallet1.get_owner_name(), wallet2.get_owner_name()))


main()
48 changes: 48 additions & 0 deletions e2e_tests/assets/wallet_program2.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# Added line in wallet_program2.py
from wallet import Wallet


def main():
name1 = input("Who is the owner of the first wallet?\n")
balance1 = float(input("What is the balance of the first wallet?\n"))
name2 = input("Who is the owner of the second wallet?\n")
balance2 = float(input("What is the balance of the second wallet?\n")) # Modified line in wallet_program2.py.
print("Creating wallets...")
wallet1 = Wallet(name1, balance1)
wallet2 = Wallet(name2, balance2)
print()
print("Wallets created:")
print(wallet1)
print(wallet2)
print()
amount_deposit1 = float(input("How much is deposited to the first wallet?\n"))
if wallet1.deposit(amount_deposit1):
print("Deposit successful!")
else:
print("Deposit failed!")
print("Balance of first wallet: {:.2f}".format(wallet1.get_balance()))
amount_deposit2 = float(input("How much is deposited to the second wallet?\n"))
if wallet2.deposit(amount_deposit2):
print("Deposit successful!")
else:
print("Deposit failed!")
print("Balance of second wallet: {:.2f}".format(wallet2.get_balance()))
amount_withdraw1 = float(input("How much is withdrawn from the first wallet?\n"))
if wallet1.withdraw(amount_withdraw1):
print("Withdraw successful!")
else:
print("Withdraw failed!")
print("Balance of first wallet: {:.2f}".format(wallet1.get_balance()))
amount_withdraw2 = float(input("How much is withdrawn from the second wallet?\n"))
if wallet2.withdraw(amount_withdraw2):
print("Withdraw successful!")
else:
print("Withdraw failed!")
print("Balance of second wallet: {:.2f}".format(wallet2.get_balance()))
if wallet1.has_more_money(wallet2):
print("The wallet of {:s} has more money than the wallet of {:s}.".format(wallet1.get_owner_name(), wallet2.get_owner_name()))
else:
print("The wallet of {:s} does not have more money than the wallet of {:s}.".format(wallet1.get_owner_name(), wallet2.get_owner_name()))


main()
36 changes: 36 additions & 0 deletions e2e_tests/helpers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
from playwright.sync_api import Page, expect
from typing import List


class File:
def __init__(self, name: str, label: str = None):
self.name = name
self.label = label if label else name


def login(page: Page, username: str, password: str):
page.goto("http://localhost:8000/")
page.get_by_role("link", name="Log in").click()
page.get_by_label("Username").click()
page.get_by_label("Username").fill(username)
page.get_by_label("Password").click()
page.get_by_label("Password").fill(password)
page.get_by_role("button", name="Log in").click()


def logout(page: Page):
page.get_by_test_id('user-menu').click()
page.get_by_role("link", name="Log out").click()


def upload_submission(page: Page, chapter_name: str, exercise_name: str, files: List[File]):
page.get_by_label("Course").get_by_role(
"link", name="Course materials").click()
page.get_by_role("link", name=chapter_name).click()
for file in files:
page.get_by_label(file.label).set_input_files(
f"e2e_tests/assets/{file.name}")
page.locator(exercise_name).get_by_role("button", name="Submit").click()
expect(page.locator("#page-modal")
).to_contain_text("Total points:", timeout=10000)
page.get_by_role("button", name="Close", exact=True).click()
60 changes: 60 additions & 0 deletions e2e_tests/test_compare_submissions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
from playwright.sync_api import Page, expect
from e2e_tests.helpers import upload_submission, login, logout, File


def test_compare_submissions(page: Page) -> None:
chapter_name = "6.3 Exercises with Python"
exercise_name = "#chapter-exercise-6"
green = "rgb(212, 237, 218)"
red = "rgb(248, 215, 218)"
login(page, "student", "student")
page.get_by_role("link", name="Def. Course Current DEF000 1.").click()
upload_submission(
page,
chapter_name=chapter_name,
exercise_name=exercise_name,
files=(File("wallet.py"), File("wallet_program.py"))
)
upload_submission(
page,
chapter_name=chapter_name,
exercise_name=exercise_name,
files=(File("wallet2.py", "wallet.py"), File(
"wallet_program2.py", "wallet_program.py"))
)
logout(page)
login(page, "assistant", "assistant")
page.get_by_role("link", name="Def. Course Current DEF000 1.").click()
page.get_by_role("link", name="Course materials").click()
page.get_by_role("link", name="6.3 Exercises with Python").first.click()
page.get_by_label("6.3.6 Wallet").get_by_role(
"button", name="View all submissions").click()
page.locator("#submission-2").get_by_role("link", name="Inspect").click()

def assert_line(filename: str, line_number: int, text: str, color: str):
line = page.get_by_test_id(f"{filename}-line-{line_number}")
expect(line).to_contain_text(text)
expect(line).to_have_css("background-color", color)

page.get_by_role("link", name="Compare to model answer").click()
expect(page.get_by_role("main")).to_contain_text(
"Comparing to the model solution.")
assert_line("wallet2.py", 3, "# Added line in wallet.py2.", green)
assert_line("wallet2.py", 6, "self.__balance = balance", red)

page.get_by_role("link", name="Compare", exact=True).click()
expect(page.get_by_role("main")).to_contain_text("Comparing to submission")
assert_line("wallet2.py", 3, "# Added line in wallet.py.", red)
assert_line("wallet2.py", 4, "# Added line in wallet.py2.", green)
page.get_by_role("tab", name="wallet_program2.py").click()
assert_line("wallet_program2.py", 10,
"# Modified line in wallet_program.py.", red)
assert_line("wallet_program2.py", 11,
"# Modified line in wallet_program2.py.", green)

page.goto(
"http://localhost:8000/def/current/programming_exercises/graderutils" +
"/programming_exercises_graderutils_iotester_exercise2/submissions/2/inspect/?compare_to=invalid"
)
expect(page.get_by_role("main")).to_contain_text(
"The file you are comparing to was not found.")
7 changes: 5 additions & 2 deletions exercise/exercise_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -439,10 +439,13 @@ def get_load_url( # pylint: disable=too-many-arguments
'lang': language,
})

def get_models(self):
entries = pick_localized(self.model_answers, get_language())
def get_models_by_language(self, language: str):
entries = pick_localized(self.model_answers, language)
return [(url,url.split('/')[-1].split('?', 1)[0]) for url in entries.split()]

def get_models(self):
return self.get_models_by_language(get_language())

def get_templates(self):
entries = pick_localized(self.templates, get_language())
return [(url,url.split('/')[-1].split('?', 1)[0]) for url in entries.split()]
Expand Down
Loading

0 comments on commit 9a22d5f

Please sign in to comment.