Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions branch_compare/.gitmastery-exercise.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"exercise_name": "branch-compare",
"tags": ["git-branch", "git-diff"],
"requires_git": true,
"requires_github": false,
"base_files": {
"answers.txt": "answers.txt"
},
"exercise_repo": {
"repo_type": "local",
"repo_name": "data-streams",
"repo_title": null,
"create_fork": null,
"init": true
}
}
1 change: 1 addition & 0 deletions branch_compare/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
See https://git-mastery.github.io/lessons/branch/exercise-branch-compare.html
Empty file added branch_compare/__init__.py
Empty file.
55 changes: 55 additions & 0 deletions branch_compare/download.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
from exercise_utils.git import add, commit, checkout
from exercise_utils.file import append_to_file, create_or_update_file

import random


def get_sequence(n=1000, digits=8, seed=None):
rng = random.Random(seed)
lo, hi = 10 ** (digits - 1), 10**digits - 1
return rng.sample(range(lo, hi + 1), k=n)


def get_modified_sequence(seq, digits=8, idx=None, seed=None):
rng = random.Random(seed)
n = len(seq)
if idx is None:
idx = rng.randrange(n)

modified = seq.copy()
seen = set(seq)
lo, hi = 10 ** (digits - 1), 10**digits - 1

old = modified[idx]
new = old
while new in seen:
new = rng.randint(lo, hi)
modified[idx] = new
return modified


def setup(verbose: bool = False):
orig_data = get_sequence()
modified_data = get_modified_sequence(orig_data)

create_or_update_file("data.txt", "")
add(["data.txt"], verbose)
commit("Add empty data.txt", verbose)
checkout("stream-1", True, verbose)

join_orig_data = "\n".join(map(str, orig_data))
append_to_file("data.txt", join_orig_data)

add(["data.txt"], verbose)
commit("Add data to data.txt", verbose)

checkout("main", False, verbose)
checkout("stream-2", True, verbose)

join_modified_data = "\n".join(map(str, modified_data))
append_to_file("data.txt", join_modified_data)

add(["data.txt"], verbose)
commit("Add data to data.txt", verbose)

checkout("main", False, verbose)
5 changes: 5 additions & 0 deletions branch_compare/res/answers.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Q: Which numbers are present in stream-1 but not in stream-2?
A:

Q: Which numbers are present in stream-2 but not in stream-1?
A:
Empty file.
43 changes: 43 additions & 0 deletions branch_compare/tests/specs/base.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
initialization:
steps:
- type: commit
empty: true
message: Set initial state
id: start
- type: commit
empty: true
message: Add empty data.txt

- type: branch
branch-name: stream-1
- type: new-file
filename: data.txt
contents: |
11111
22222
12345
- type: add
files:
- data.txt
- type: commit
message: Add data to data.txt

- type: checkout
branch-name: main

- type: branch
branch-name: stream-2
- type: new-file
filename: data.txt
contents: |
11111
22222
98765
- type: add
files:
- data.txt
- type: commit
message: Add data to data.txt

- type: checkout
branch-name: main
55 changes: 55 additions & 0 deletions branch_compare/tests/specs/extra_commit_on_stream1.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
initialization:
steps:
- type: commit
empty: true
message: Set initial state
id: start
- type: commit
empty: true
message: Add empty data.txt

- type: branch
branch-name: stream-1
- type: new-file
filename: data.txt
contents: |
11111
22222
12345
- type: add
files:
- data.txt
- type: commit
message: Add data to data.txt

- type: checkout
branch-name: main

- type: branch
branch-name: stream-2
- type: new-file
filename: data.txt
contents: |
11111
22222
98765
- type: add
files:
- data.txt
- type: commit
message: Add data to data.txt

- type: checkout
branch-name: stream-1
- type: new-file
filename: extra.txt
contents: |
extra content
- type: add
files:
- extra.txt
- type: commit
message: Extra change on stream-1

- type: checkout
branch-name: main
57 changes: 57 additions & 0 deletions branch_compare/tests/test_verify.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
from git_autograder import GitAutograderStatus, GitAutograderTestLoader, assert_output
from git_autograder.answers.rules.has_exact_value_rule import HasExactValueRule

from ..verify import verify, QUESTION_ONE, QUESTION_TWO, NO_CHANGES_ERROR

REPOSITORY_NAME = "branch-compare"

loader = GitAutograderTestLoader(__file__, REPOSITORY_NAME, verify)


def test_base():
with loader.load(
"specs/base.yml",
"start",
mock_answers={
QUESTION_ONE: "12345",
QUESTION_TWO: "98765",
},
) as output:
assert_output(output, GitAutograderStatus.SUCCESSFUL)


def test_wrong_stream1_diff():
with loader.load(
"specs/base.yml",
"start",
mock_answers={
QUESTION_ONE: "99999",
QUESTION_TWO: "98765",
},
) as output:
assert_output(
output,
GitAutograderStatus.UNSUCCESSFUL,
[HasExactValueRule.NOT_EXACT.format(question=QUESTION_ONE)],
)


def test_wrong_stream2_diff():
with loader.load(
"specs/base.yml",
"start",
mock_answers={
QUESTION_ONE: "12345",
QUESTION_TWO: "99999",
},
) as output:
assert_output(
output,
GitAutograderStatus.UNSUCCESSFUL,
[HasExactValueRule.NOT_EXACT.format(question=QUESTION_TWO)],
)


def test_changes_made_extra_commit():
with loader.load("specs/extra_commit_on_stream1.yml", "start") as output:
assert_output(output, GitAutograderStatus.UNSUCCESSFUL, [NO_CHANGES_ERROR])
74 changes: 74 additions & 0 deletions branch_compare/verify.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
from git_autograder import (
GitAutograderBranch,
GitAutograderOutput,
GitAutograderExercise,
GitAutograderStatus,
)

from git_autograder.answers.rules import HasExactValueRule, NotEmptyRule


QUESTION_ONE = "Which numbers are present in stream-1 but not in stream-2?"
QUESTION_TWO = "Which numbers are present in stream-2 but not in stream-1?"
NO_CHANGES_ERROR = (
"No changes are supposed to be made to the two branches in this exercise"
)

FILE_PATH = "data.txt"
BRANCH_1 = "stream-1"
BRANCH_2 = "stream-2"


def has_made_changes(branch: GitAutograderBranch, expected_commits: int) -> bool:
"""Check branch has same number of commits as expected."""

commits = branch.commits
return len(commits) != expected_commits


def get_branch_diff(exercise: GitAutograderExercise, branch1: str, branch2: str) -> str:
"""Get a value present in branch1 but not in branch2."""
exercise.repo.branches.branch(branch1).checkout()
with exercise.repo.files.file(FILE_PATH) as f1:
contents1 = f1.read()

exercise.repo.branches.branch(branch2).checkout()
with exercise.repo.files.file(FILE_PATH) as f2:
contents2 = f2.read()

exercise.repo.branches.branch("main").checkout()

set1 = {line.strip() for line in contents1.splitlines() if line.strip()}
set2 = {line.strip() for line in contents2.splitlines() if line.strip()}
diff = set1 - set2
return str(diff.pop())


def verify(exercise: GitAutograderExercise) -> GitAutograderOutput:
branch_1 = exercise.repo.branches.branch(BRANCH_1)
branch_2 = exercise.repo.branches.branch(BRANCH_2)
if (
not branch_1
or not branch_2
or has_made_changes(branch_1, 3)
or has_made_changes(branch_2, 3)
):
raise exercise.wrong_answer([NO_CHANGES_ERROR])

ans_1 = get_branch_diff(exercise, BRANCH_1, BRANCH_2)
ans_2 = get_branch_diff(exercise, BRANCH_2, BRANCH_1)

exercise.answers.add_validation(
QUESTION_ONE,
NotEmptyRule(),
HasExactValueRule(ans_1, is_case_sensitive=False),
).add_validation(
QUESTION_TWO,
NotEmptyRule(),
HasExactValueRule(ans_2, is_case_sensitive=False),
).validate()

return exercise.to_output(
["Great work comparing the branches successfully!"],
GitAutograderStatus.SUCCESSFUL,
)