Skip to content

Commit c8c8696

Browse files
[branch-compare] Implement exercise T6L2/branch-compare (#115)
# Exercise Review ## Exercise Discussion #69 ## 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)? --------- Co-authored-by: jovnc <[email protected]>
1 parent d079cc3 commit c8c8696

File tree

10 files changed

+306
-0
lines changed

10 files changed

+306
-0
lines changed
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{
2+
"exercise_name": "branch-compare",
3+
"tags": ["git-branch", "git-diff"],
4+
"requires_git": true,
5+
"requires_github": false,
6+
"base_files": {
7+
"answers.txt": "answers.txt"
8+
},
9+
"exercise_repo": {
10+
"repo_type": "local",
11+
"repo_name": "data-streams",
12+
"repo_title": null,
13+
"create_fork": null,
14+
"init": true
15+
}
16+
}

branch_compare/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
See https://git-mastery.github.io/lessons/branch/exercise-branch-compare.html

branch_compare/__init__.py

Whitespace-only changes.

branch_compare/download.py

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
from exercise_utils.git import add, commit, checkout
2+
from exercise_utils.file import append_to_file, create_or_update_file
3+
4+
import random
5+
6+
7+
def get_sequence(n=1000, digits=8, seed=None):
8+
rng = random.Random(seed)
9+
lo, hi = 10 ** (digits - 1), 10**digits - 1
10+
return rng.sample(range(lo, hi + 1), k=n)
11+
12+
13+
def get_modified_sequence(seq, digits=8, idx=None, seed=None):
14+
rng = random.Random(seed)
15+
n = len(seq)
16+
if idx is None:
17+
idx = rng.randrange(n)
18+
19+
modified = seq.copy()
20+
seen = set(seq)
21+
lo, hi = 10 ** (digits - 1), 10**digits - 1
22+
23+
old = modified[idx]
24+
new = old
25+
while new in seen:
26+
new = rng.randint(lo, hi)
27+
modified[idx] = new
28+
return modified
29+
30+
31+
def setup(verbose: bool = False):
32+
orig_data = get_sequence()
33+
modified_data = get_modified_sequence(orig_data)
34+
35+
create_or_update_file("data.txt", "")
36+
add(["data.txt"], verbose)
37+
commit("Add empty data.txt", verbose)
38+
checkout("stream-1", True, verbose)
39+
40+
join_orig_data = "\n".join(map(str, orig_data))
41+
append_to_file("data.txt", join_orig_data)
42+
43+
add(["data.txt"], verbose)
44+
commit("Add data to data.txt", verbose)
45+
46+
checkout("main", False, verbose)
47+
checkout("stream-2", True, verbose)
48+
49+
join_modified_data = "\n".join(map(str, modified_data))
50+
append_to_file("data.txt", join_modified_data)
51+
52+
add(["data.txt"], verbose)
53+
commit("Add data to data.txt", verbose)
54+
55+
checkout("main", False, verbose)

branch_compare/res/answers.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
Q: Which numbers are present in stream-1 but not in stream-2?
2+
A:
3+
4+
Q: Which numbers are present in stream-2 but not in stream-1?
5+
A:

branch_compare/tests/__init__.py

Whitespace-only changes.
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
initialization:
2+
steps:
3+
- type: commit
4+
empty: true
5+
message: Set initial state
6+
id: start
7+
- type: commit
8+
empty: true
9+
message: Add empty data.txt
10+
11+
- type: branch
12+
branch-name: stream-1
13+
- type: new-file
14+
filename: data.txt
15+
contents: |
16+
11111
17+
22222
18+
12345
19+
- type: add
20+
files:
21+
- data.txt
22+
- type: commit
23+
message: Add data to data.txt
24+
25+
- type: checkout
26+
branch-name: main
27+
28+
- type: branch
29+
branch-name: stream-2
30+
- type: new-file
31+
filename: data.txt
32+
contents: |
33+
11111
34+
22222
35+
98765
36+
- type: add
37+
files:
38+
- data.txt
39+
- type: commit
40+
message: Add data to data.txt
41+
42+
- type: checkout
43+
branch-name: main
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
initialization:
2+
steps:
3+
- type: commit
4+
empty: true
5+
message: Set initial state
6+
id: start
7+
- type: commit
8+
empty: true
9+
message: Add empty data.txt
10+
11+
- type: branch
12+
branch-name: stream-1
13+
- type: new-file
14+
filename: data.txt
15+
contents: |
16+
11111
17+
22222
18+
12345
19+
- type: add
20+
files:
21+
- data.txt
22+
- type: commit
23+
message: Add data to data.txt
24+
25+
- type: checkout
26+
branch-name: main
27+
28+
- type: branch
29+
branch-name: stream-2
30+
- type: new-file
31+
filename: data.txt
32+
contents: |
33+
11111
34+
22222
35+
98765
36+
- type: add
37+
files:
38+
- data.txt
39+
- type: commit
40+
message: Add data to data.txt
41+
42+
- type: checkout
43+
branch-name: stream-1
44+
- type: new-file
45+
filename: extra.txt
46+
contents: |
47+
extra content
48+
- type: add
49+
files:
50+
- extra.txt
51+
- type: commit
52+
message: Extra change on stream-1
53+
54+
- type: checkout
55+
branch-name: main
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
from git_autograder import GitAutograderStatus, GitAutograderTestLoader, assert_output
2+
from git_autograder.answers.rules.has_exact_value_rule import HasExactValueRule
3+
4+
from ..verify import verify, QUESTION_ONE, QUESTION_TWO, NO_CHANGES_ERROR
5+
6+
REPOSITORY_NAME = "branch-compare"
7+
8+
loader = GitAutograderTestLoader(__file__, REPOSITORY_NAME, verify)
9+
10+
11+
def test_base():
12+
with loader.load(
13+
"specs/base.yml",
14+
"start",
15+
mock_answers={
16+
QUESTION_ONE: "12345",
17+
QUESTION_TWO: "98765",
18+
},
19+
) as output:
20+
assert_output(output, GitAutograderStatus.SUCCESSFUL)
21+
22+
23+
def test_wrong_stream1_diff():
24+
with loader.load(
25+
"specs/base.yml",
26+
"start",
27+
mock_answers={
28+
QUESTION_ONE: "99999",
29+
QUESTION_TWO: "98765",
30+
},
31+
) as output:
32+
assert_output(
33+
output,
34+
GitAutograderStatus.UNSUCCESSFUL,
35+
[HasExactValueRule.NOT_EXACT.format(question=QUESTION_ONE)],
36+
)
37+
38+
39+
def test_wrong_stream2_diff():
40+
with loader.load(
41+
"specs/base.yml",
42+
"start",
43+
mock_answers={
44+
QUESTION_ONE: "12345",
45+
QUESTION_TWO: "99999",
46+
},
47+
) as output:
48+
assert_output(
49+
output,
50+
GitAutograderStatus.UNSUCCESSFUL,
51+
[HasExactValueRule.NOT_EXACT.format(question=QUESTION_TWO)],
52+
)
53+
54+
55+
def test_changes_made_extra_commit():
56+
with loader.load("specs/extra_commit_on_stream1.yml", "start") as output:
57+
assert_output(output, GitAutograderStatus.UNSUCCESSFUL, [NO_CHANGES_ERROR])

branch_compare/verify.py

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
from git_autograder import (
2+
GitAutograderBranch,
3+
GitAutograderOutput,
4+
GitAutograderExercise,
5+
GitAutograderStatus,
6+
)
7+
8+
from git_autograder.answers.rules import HasExactValueRule, NotEmptyRule
9+
10+
11+
QUESTION_ONE = "Which numbers are present in stream-1 but not in stream-2?"
12+
QUESTION_TWO = "Which numbers are present in stream-2 but not in stream-1?"
13+
NO_CHANGES_ERROR = (
14+
"No changes are supposed to be made to the two branches in this exercise"
15+
)
16+
17+
FILE_PATH = "data.txt"
18+
BRANCH_1 = "stream-1"
19+
BRANCH_2 = "stream-2"
20+
21+
22+
def has_made_changes(branch: GitAutograderBranch, expected_commits: int) -> bool:
23+
"""Check branch has same number of commits as expected."""
24+
25+
commits = branch.commits
26+
return len(commits) != expected_commits
27+
28+
29+
def get_branch_diff(exercise: GitAutograderExercise, branch1: str, branch2: str) -> str:
30+
"""Get a value present in branch1 but not in branch2."""
31+
exercise.repo.branches.branch(branch1).checkout()
32+
with exercise.repo.files.file(FILE_PATH) as f1:
33+
contents1 = f1.read()
34+
35+
exercise.repo.branches.branch(branch2).checkout()
36+
with exercise.repo.files.file(FILE_PATH) as f2:
37+
contents2 = f2.read()
38+
39+
exercise.repo.branches.branch("main").checkout()
40+
41+
set1 = {line.strip() for line in contents1.splitlines() if line.strip()}
42+
set2 = {line.strip() for line in contents2.splitlines() if line.strip()}
43+
diff = set1 - set2
44+
return str(diff.pop())
45+
46+
47+
def verify(exercise: GitAutograderExercise) -> GitAutograderOutput:
48+
branch_1 = exercise.repo.branches.branch(BRANCH_1)
49+
branch_2 = exercise.repo.branches.branch(BRANCH_2)
50+
if (
51+
not branch_1
52+
or not branch_2
53+
or has_made_changes(branch_1, 3)
54+
or has_made_changes(branch_2, 3)
55+
):
56+
raise exercise.wrong_answer([NO_CHANGES_ERROR])
57+
58+
ans_1 = get_branch_diff(exercise, BRANCH_1, BRANCH_2)
59+
ans_2 = get_branch_diff(exercise, BRANCH_2, BRANCH_1)
60+
61+
exercise.answers.add_validation(
62+
QUESTION_ONE,
63+
NotEmptyRule(),
64+
HasExactValueRule(ans_1, is_case_sensitive=False),
65+
).add_validation(
66+
QUESTION_TWO,
67+
NotEmptyRule(),
68+
HasExactValueRule(ans_2, is_case_sensitive=False),
69+
).validate()
70+
71+
return exercise.to_output(
72+
["Great work comparing the branches successfully!"],
73+
GitAutograderStatus.SUCCESSFUL,
74+
)

0 commit comments

Comments
 (0)