Skip to content

Commit d266fd9

Browse files
authored
[mix-messy-graph] Add checkout and validate file content in verify logic (#202)
# Exercise Review ## Exercise Discussion Fixes #71 ## Checklist - [ ] If you require a new remote repository on the `Git-Mastery` organization, have you [created a request](https://github.com/git-mastery/exercises/issues/new?template=request_exercise_repository.md) for it? - [X] Have you written unit tests using [`repo-smith`](https://github.com/git-mastery/repo-smith) to validate the exercise grading scheme? - [X] Have you tested the download script using `test-download.sh`? - [X] Have you verified that this exercise does not already exist or is not currently in review? - [ ] Did you introduce a new grading mechanism that should belong to [`git-autograder`](https://github.com/git-mastery/git-autograder)? - [ ] Did you introduce a new dependency that should belong to [`app`](https://github.com/git-mastery/app)?
1 parent c947bd9 commit d266fd9

File tree

2 files changed

+166
-26
lines changed

2 files changed

+166
-26
lines changed

mix_messy_graph/test_verify.py

Lines changed: 87 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515

1616
loader = GitAutograderTestLoader(REPOSITORY_NAME, verify)
1717

18-
FEATURES = """
18+
FEATURES_FILE_CONTENT_DELETE_COMMIT = """
1919
# Features
2020
2121
## Creating Books
@@ -32,22 +32,95 @@
3232
Allows deleting books.
3333
"""
3434

35+
FEATURES_FILE_CONTENT_SEARCH_COMMIT = """
36+
# Features
37+
38+
## Creating Books
39+
40+
Allows creating one book at a time.
41+
42+
## Searching for Books
43+
44+
Allows searching for books by keywords.
45+
Works only for book titles.
46+
"""
47+
48+
FEATURES_FILE_CONTENT_FIX_HEADINGS_COMMIT = """
49+
# Features
50+
51+
## Creating Books
52+
53+
Allows creating one book at a time.
54+
"""
55+
56+
57+
FEATURES_FILE_CONTENT_CREATE_COMMIT = """
58+
# Features
59+
60+
## Create Book
61+
62+
Allows creating one book at a time.
63+
"""
64+
65+
FEATURES_FILE_CONTENT_FEATURES_COMMIT = """
66+
# Features
67+
"""
68+
3569

3670
def test_base():
3771
with loader.start() as (test, rs):
38-
rs.git.commit(message="Add features.md", allow_empty=True)
72+
rs.files.create_or_update("features.md", FEATURES_FILE_CONTENT_FEATURES_COMMIT)
73+
rs.git.add("features.md")
74+
rs.git.commit(message="Add features.md")
3975
rs.helper(GitMasteryHelper).create_start_tag()
40-
rs.git.commit(message="Mention feature for creating books", allow_empty=True)
76+
77+
rs.files.create_or_update("features.md", FEATURES_FILE_CONTENT_CREATE_COMMIT)
78+
rs.git.add("features.md")
79+
rs.git.commit(message="Mention feature for creating books")
4180
rs.git.tag("v1.0")
42-
rs.git.commit(message="Fix phrasing of heading", allow_empty=True)
43-
rs.git.commit(message="Add the search feature", allow_empty=True)
44-
rs.git.commit(message="Add the delete feature", allow_empty=True)
45-
rs.files.create_or_update("features.md", FEATURES)
81+
82+
rs.files.create_or_update("features.md", FEATURES_FILE_CONTENT_FIX_HEADINGS_COMMIT)
83+
rs.git.add("features.md")
84+
rs.git.commit(message="Fix phrasing of heading")
85+
86+
rs.files.create_or_update("features.md", FEATURES_FILE_CONTENT_SEARCH_COMMIT)
87+
rs.git.add("features.md")
88+
rs.git.commit(message="Add the search feature")
89+
90+
rs.files.create_or_update("features.md", FEATURES_FILE_CONTENT_DELETE_COMMIT)
91+
rs.git.add("features.md")
92+
rs.git.commit(message="Add the delete feature")
4693

4794
output = test.run()
4895
assert_output(output, GitAutograderStatus.SUCCESSFUL)
4996

5097

98+
def test_invalid_features_content():
99+
with loader.start() as (test, rs):
100+
rs.files.create_or_update("features.md", FEATURES_FILE_CONTENT_FEATURES_COMMIT)
101+
rs.git.add("features.md")
102+
rs.git.commit(message="Add features.md")
103+
rs.helper(GitMasteryHelper).create_start_tag()
104+
105+
rs.files.create_or_update("features.md", FEATURES_FILE_CONTENT_CREATE_COMMIT)
106+
rs.git.add("features.md")
107+
rs.git.commit(message="Mention feature for creating books")
108+
rs.git.tag("v1.0")
109+
110+
rs.git.commit(message="Fix phrasing of heading", allow_empty=True)
111+
112+
rs.files.create_or_update("features.md", FEATURES_FILE_CONTENT_SEARCH_COMMIT)
113+
rs.git.add("features.md")
114+
rs.git.commit(message="Add the search feature")
115+
116+
rs.files.create_or_update("features.md", FEATURES_FILE_CONTENT_DELETE_COMMIT)
117+
rs.git.add("features.md")
118+
rs.git.commit(message="Add the delete feature")
119+
120+
output = test.run()
121+
assert_output(output, GitAutograderStatus.UNSUCCESSFUL, [FEATURES_FILE_CONTENT_INVALID.format(commit="Fix phrasing of heading")])
122+
123+
51124
def test_non_squash_merge_used():
52125
with loader.start() as (test, rs):
53126
rs.git.commit(message="Add features.md", allow_empty=True)
@@ -62,7 +135,7 @@ def test_non_squash_merge_used():
62135
rs.git.merge("feature-search")
63136

64137
rs.git.commit(message="Add the delete feature", allow_empty=True)
65-
rs.files.create_or_update("features.md", FEATURES)
138+
rs.files.create_or_update("features.md", FEATURES_FILE_CONTENT_DELETE_COMMIT)
66139

67140
output = test.run()
68141
# This would fail because the squash merge changes the commit messages and the contents
@@ -91,7 +164,7 @@ def test_non_squash_merge_used_2():
91164
rs.git.merge("feature-search", no_ff=True)
92165

93166
rs.git.commit(message="Add the delete feature", allow_empty=True)
94-
rs.files.create_or_update("features.md", FEATURES)
167+
rs.files.create_or_update("features.md", FEATURES_FILE_CONTENT_DELETE_COMMIT)
95168

96169
output = test.run()
97170
assert_output(
@@ -110,7 +183,7 @@ def test_wrong_commit_message():
110183
rs.git.commit(message="Fix phrasing of heading", allow_empty=True)
111184
rs.git.commit(message="Add the search feature!", allow_empty=True)
112185
rs.git.commit(message="Add the delete feature", allow_empty=True)
113-
rs.files.create_or_update("features.md", FEATURES)
186+
rs.files.create_or_update("features.md", FEATURES_FILE_CONTENT_DELETE_COMMIT)
114187

115188
output = test.run()
116189
assert_output(
@@ -132,7 +205,7 @@ def test_missing_commit():
132205
rs.git.tag("v1.0")
133206
rs.git.commit(message="Fix phrasing of heading", allow_empty=True)
134207
rs.git.commit(message="Add the search feature", allow_empty=True)
135-
rs.files.create_or_update("features.md", FEATURES)
208+
rs.files.create_or_update("features.md", FEATURES_FILE_CONTENT_DELETE_COMMIT)
136209

137210
output = test.run()
138211
assert_output(
@@ -160,7 +233,7 @@ def test_branches_not_deleted():
160233
rs.git.branch("feature-delete")
161234
rs.git.branch("list")
162235

163-
rs.files.create_or_update("features.md", FEATURES)
236+
rs.files.create_or_update("features.md", FEATURES_FILE_CONTENT_DELETE_COMMIT)
164237

165238
output = test.run()
166239
assert_output(
@@ -183,11 +256,11 @@ def test_features_content_invalid():
183256
rs.git.commit(message="Fix phrasing of heading", allow_empty=True)
184257
rs.git.commit(message="Add the search feature", allow_empty=True)
185258
rs.git.commit(message="Add the delete feature", allow_empty=True)
186-
rs.files.create_or_update("features.md", FEATURES[0])
259+
rs.files.create_or_update("features.md", FEATURES_FILE_CONTENT_DELETE_COMMIT[0])
187260

188261
output = test.run()
189262
assert_output(
190263
output,
191264
GitAutograderStatus.UNSUCCESSFUL,
192-
[FEATURES_FILE_CONTENT_INVALID],
265+
[FEATURES_FILE_CONTENT_INVALID.format(commit="Add features.md")],
193266
)

mix_messy_graph/verify.py

Lines changed: 79 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1+
from typing import Optional, List
12
from git_autograder import (
23
GitAutograderOutput,
34
GitAutograderExercise,
45
GitAutograderStatus,
6+
GitAutograderCommit
57
)
68
from itertools import zip_longest
79

@@ -24,7 +26,7 @@
2426
LIST_BRANCH_STILL_EXISTS = "Branch 'list' still exists."
2527

2628
MISSING_FEATURES_FILE = "You are missing 'features.md'!"
27-
FEATURES_FILE_CONTENT_INVALID = "Contents of 'features.md' is not valid! Try again!"
29+
FEATURES_FILE_CONTENT_INVALID = "Contents of 'features.md' is not valid at commit with message '{commit}'! Try again!"
2830

2931
EXPECTED_COMMIT_MESSAGES = [
3032
"Add features.md",
@@ -34,7 +36,7 @@
3436
"Add the delete feature",
3537
]
3638

37-
EXPECTED_LINES = [
39+
EXPECTED_LINES_DELETE_COMMIT = [
3840
"# Features",
3941
"## Creating Books",
4042
"Allows creating one book at a time.",
@@ -45,20 +47,74 @@
4547
"Allows deleting books.",
4648
]
4749

50+
EXPECTED_LINES_SEARCH_COMMIT = [
51+
"# Features",
52+
"## Creating Books",
53+
"Allows creating one book at a time.",
54+
"## Searching for Books",
55+
"Allows searching for books by keywords.",
56+
"Works only for book titles.",
57+
]
58+
59+
EXPECTED_LINES_FIX_HEADING_COMMIT = [
60+
"# Features",
61+
"## Creating Books",
62+
"Allows creating one book at a time.",
63+
]
64+
65+
EXPECTED_LINES_CREATE_BOOK_COMMIT = [
66+
"# Features",
67+
"## Create Book",
68+
"Allows creating one book at a time.",
69+
]
70+
71+
EXPECTED_LINES_FEATURES_COMMIT = [
72+
"# Features",
73+
]
74+
4875

4976
def ensure_str(val) -> str:
5077
if isinstance(val, bytes):
5178
return val.decode("utf-8", errors="replace").strip()
5279
return str(val).strip()
5380

5481

82+
def get_commit_from_message(
83+
commits: List[GitAutograderCommit], message: str
84+
) -> Optional[GitAutograderCommit]:
85+
"""Find a commit with the given message from a list of commits."""
86+
for commit in commits:
87+
if message.strip() == commit.commit.message.strip():
88+
return commit
89+
return None
90+
91+
92+
def verify_commit_file_content(exercise: GitAutograderExercise, commit: GitAutograderCommit | None, file_name: str, expected_content: List[str]):
93+
"""Checkout to specific commit and verify that the file content of the given commit matches the expected content."""
94+
if not commit:
95+
return
96+
commit.checkout()
97+
with exercise.repo.files.file_or_none(file_name) as file:
98+
if file is None:
99+
raise exercise.wrong_answer([MISSING_FEATURES_FILE])
100+
101+
contents = [line.strip() for line in file.readlines() if line.strip() != ""]
102+
if contents != expected_content:
103+
raise exercise.wrong_answer([FEATURES_FILE_CONTENT_INVALID.format(commit=commit.commit.message.strip())])
104+
exercise.repo.branches.branch("main").checkout()
105+
106+
55107
def verify(exercise: GitAutograderExercise) -> GitAutograderOutput:
56108
main_branch = exercise.repo.branches.branch("main")
109+
110+
# Verify that there are no merge commits
57111
merge_commits = [c for c in main_branch.commits if len(c.parents) > 1]
58112
if merge_commits:
59113
raise exercise.wrong_answer([SQUASH_NOT_USED])
60114

61-
commit_messages = [ensure_str(c.commit.message) for c in main_branch.commits][::-1]
115+
# Verify that the commit messages are correct
116+
commits = main_branch.commits
117+
commit_messages = [ensure_str(c.commit.message) for c in commits][::-1]
62118
for expected, given in zip_longest(EXPECTED_COMMIT_MESSAGES, commit_messages):
63119
if expected != given:
64120
raise exercise.wrong_answer(
@@ -69,6 +125,7 @@ def verify(exercise: GitAutograderExercise) -> GitAutograderOutput:
69125
]
70126
)
71127

128+
# Verify that the branches are deleted
72129
feature_search_branch = exercise.repo.branches.branch_or_none("feature-search")
73130
feature_delete_branch = exercise.repo.branches.branch_or_none("feature-delete")
74131
list_branch = exercise.repo.branches.branch_or_none("list")
@@ -83,14 +140,24 @@ def verify(exercise: GitAutograderExercise) -> GitAutograderOutput:
83140
if branch_exists_messages:
84141
raise exercise.wrong_answer(branch_exists_messages)
85142

86-
with exercise.repo.files.file_or_none("features.md") as features_file:
87-
if features_file is None:
88-
raise exercise.wrong_answer([MISSING_FEATURES_FILE])
143+
# Verify that the features.md file is correct
144+
# Checkout to specific commit to verify the contents of features.md
145+
features_commit = get_commit_from_message(commits, "Add features.md")
146+
verify_commit_file_content(exercise, features_commit, "features.md", EXPECTED_LINES_FEATURES_COMMIT)
147+
148+
create_books_commit = get_commit_from_message(commits, "Mention feature for creating books")
149+
verify_commit_file_content(exercise, create_books_commit, "features.md", EXPECTED_LINES_CREATE_BOOK_COMMIT)
150+
151+
fix_heading_commit = get_commit_from_message(commits, "Fix phrasing of heading")
152+
verify_commit_file_content(exercise, fix_heading_commit, "features.md", EXPECTED_LINES_FIX_HEADING_COMMIT)
153+
154+
add_search_commit = get_commit_from_message(commits, "Add the search feature")
155+
verify_commit_file_content(exercise, add_search_commit, "features.md", EXPECTED_LINES_SEARCH_COMMIT)
89156

90-
contents = [
91-
line.strip() for line in features_file.readlines() if line.strip() != ""
92-
]
93-
if contents != EXPECTED_LINES:
94-
raise exercise.wrong_answer([FEATURES_FILE_CONTENT_INVALID])
157+
delete_feature_commit = get_commit_from_message(commits, "Add the delete feature")
158+
verify_commit_file_content(exercise, delete_feature_commit, "features.md", EXPECTED_LINES_DELETE_COMMIT)
95159

96-
return exercise.to_output([], GitAutograderStatus.SUCCESSFUL)
160+
return exercise.to_output(
161+
["You have successfully completed the exercise!"],
162+
GitAutograderStatus.SUCCESSFUL
163+
)

0 commit comments

Comments
 (0)