-
Notifications
You must be signed in to change notification settings - Fork 25
Implement exercise T6L2/branch-compare #115
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
ff275e1
6fe5566
0b2d808
ff7c730
73c727d
00cb16c
9e662fb
9eccc4d
01b9415
62bf2da
a63e7cc
43d1034
dee23e0
8d4e276
b21b1e2
85505df
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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 | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change | ||
|---|---|---|---|---|
| @@ -0,0 +1,9 @@ | ||||
| # branch-compare | ||||
|
|
||||
| ## Task | ||||
|
|
||||
| You are recording a numerical data stream from two sources. The data are stored in `data.txt`, using a different branch for each stream. The two data streams are supposed to be identical but can vary on rare occasions. | ||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This line should be above |
||||
|
|
||||
| Answer the questions given in `answers.txt`. | ||||
|
|
||||
| Run `gitmastery verify` to check if your answers are correct. | ||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
We can remove this line |
||||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,62 @@ | ||||||
| from exercise_utils.cli import run_command | ||||||
| from exercise_utils.git import add, commit, checkout | ||||||
| from exercise_utils.file import append_to_file | ||||||
| from exercise_utils.gitmastery import create_start_tag | ||||||
|
|
||||||
| import random | ||||||
|
|
||||||
| __resources__ = {} | ||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
This can be removed if not used |
||||||
|
|
||||||
| 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): | ||||||
|
|
||||||
| create_start_tag(verbose) | ||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
This can be removed as not needed here |
||||||
|
|
||||||
| orig_data = get_sequence() | ||||||
| modified_data = get_modified_sequence(orig_data) | ||||||
|
|
||||||
| run_command(["touch", "data.txt"], verbose) | ||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Prefer to use
Suggested change
|
||||||
| add(["data.txt"], verbose) | ||||||
| commit("Add empty data.txt", verbose) | ||||||
| checkout("stream-1", True, verbose) | ||||||
|
|
||||||
| for i in orig_data: | ||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I believe it would be more efficient to create the entire string first using |
||||||
| append_to_file("data.txt", str(i)+"\n") | ||||||
|
|
||||||
| add(["data.txt"], verbose) | ||||||
| commit("Add data to data.txt", verbose) | ||||||
|
|
||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
|
|
||||||
| checkout("main", False, verbose) | ||||||
| checkout("stream-2", True, verbose) | ||||||
|
|
||||||
| for i in modified_data: | ||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same as above |
||||||
| append_to_file("data.txt", str(i)+"\n") | ||||||
|
|
||||||
| add(["data.txt"], verbose) | ||||||
| commit("Add data to data.txt", verbose) | ||||||
|
|
||||||
| checkout("main", False, verbose) | ||||||
|
|
||||||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,5 @@ | ||||||
| Q: Which number (write only one number) is present in branch stream-1 but not in branch stream-2? | ||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
Should align with questions in issue |
||||||
| A: | ||||||
|
|
||||||
| Q: Which number (write only one number) is present in branch stream-2 but not in branch stream-1? | ||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
| A: | ||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,44 @@ | ||
| initialization: | ||
| steps: | ||
| - type: commit | ||
| empty: true | ||
| message: Empty commit | ||
| id: start | ||
|
|
||
| # Create stream-1 with data.txt containing a unique number 12345 | ||
| - 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.txt on stream-1 | ||
|
|
||
| # Return to main | ||
| - type: checkout | ||
| branch-name: main | ||
|
|
||
| # Create stream-2 with data.txt containing a unique number 98765 | ||
| - 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.txt on stream-2 | ||
|
|
||
| # Return to main at the end for verify to start cleanly | ||
| - type: checkout | ||
| branch-name: main |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,55 @@ | ||
| initialization: | ||
| steps: | ||
| - type: commit | ||
| empty: true | ||
| message: Empty commit | ||
| id: start | ||
|
|
||
| # Base setup identical to base.yml | ||
| - 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.txt on stream-1 | ||
|
|
||
| - 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.txt on stream-2 | ||
|
|
||
| # Make an extra change on stream-1 to simulate user changes | ||
| - 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 | ||
|
|
||
| # Return to main at the end | ||
| - type: checkout | ||
| branch-name: main |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,55 @@ | ||
| 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]) |
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,83 @@ | ||||||
| from git_autograder import ( | ||||||
| GitAutograderOutput, | ||||||
| GitAutograderExercise, | ||||||
| GitAutograderStatus, | ||||||
| ) | ||||||
|
|
||||||
| from git_autograder.answers.rules import HasExactValueRule, NotEmptyRule | ||||||
|
|
||||||
|
|
||||||
| QUESTION_ONE = "Which number (write only one number) is present in branch stream-1 but not in branch stream-2?" | ||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
| QUESTION_TWO = "Which number (write only one number) is present in branch stream-2 but not in branch stream-1?" | ||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
| 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(exercise: GitAutograderExercise) -> bool: | ||||||
| repo = exercise.repo.repo | ||||||
|
|
||||||
| for bname in (BRANCH_1, BRANCH_2): | ||||||
| if not exercise.repo.branches.has_branch(bname): | ||||||
| return True | ||||||
|
|
||||||
| head = repo.commit(bname) | ||||||
|
|
||||||
| if len(head.parents) != 1: | ||||||
| return True | ||||||
|
|
||||||
| # Count commits unique to branch relative to main | ||||||
| merge_bases = repo.merge_base(bname, "main") | ||||||
| if not merge_bases: | ||||||
| return True | ||||||
| base = merge_bases[0] | ||||||
| unique_commits = list(repo.iter_commits(f"{base.hexsha}..{bname}")) | ||||||
| if len(unique_commits) != 1: | ||||||
| return True | ||||||
|
|
||||||
| return False | ||||||
|
|
||||||
| def get_branch_diff(exercise: GitAutograderExercise, branch1: str, branch2: str) -> str: | ||||||
| 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 get_stream1_diff(exercise: GitAutograderExercise) -> str: | ||||||
| return get_branch_diff(exercise, BRANCH_1, BRANCH_2) | ||||||
|
|
||||||
| def get_stream2_diff(exercise: GitAutograderExercise) -> str: | ||||||
| return get_branch_diff(exercise, BRANCH_2, BRANCH_1) | ||||||
|
|
||||||
| def verify(exercise: GitAutograderExercise) -> GitAutograderOutput: | ||||||
|
|
||||||
| if has_made_changes(exercise): | ||||||
| return exercise.to_output([NO_CHANGES_ERROR], GitAutograderStatus.UNSUCCESSFUL) | ||||||
|
|
||||||
| exercise.repo.branches.branch("main").checkout() | ||||||
|
|
||||||
| ans_1 = get_stream1_diff(exercise) | ||||||
| ans_2 = get_stream2_diff(exercise) | ||||||
|
|
||||||
| exercise.answers.add_validation( | ||||||
| QUESTION_ONE, | ||||||
| NotEmptyRule(), | ||||||
| HasExactValueRule(ans_1), | ||||||
| ).add_validation( | ||||||
| QUESTION_TWO, | ||||||
| NotEmptyRule(), | ||||||
| HasExactValueRule(ans_2), | ||||||
| ).validate() | ||||||
|
|
||||||
| return exercise.to_output(["Great work comparing the branches successfully!"], GitAutograderStatus.SUCCESSFUL) | ||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.