IMPLEMENTS implementations ========================== Implementation sections for workspaces -------------------------------------- We'll use `$DATADIR/workspace` as the workspace directory that is used. IMPLEMENTS GIVEN no workspace true IMPLEMENTS GIVEN an empty workspace directory mkdir "$DATADIR/workspace" IMPLEMENTS GIVEN a non-empty workspace directory mkdir "$DATADIR/workspace" touch "$DATADIR/workspace/random-file" We run `morph init` in two different ways: either the simple way, letting yarn catch errors, or in a way that catches the error so we can test it later in a THEN step. IMPLEMENTS WHEN the user (attempts to initialise|initialises) a workspace set init "$DATADIR/workspace" if [ $MATCH_1 == "initialises" ]; then run_morph "$@" else attempt_morph "$@"; fi IMPLEMENTS THEN morph failed case $(cat "$DATADIR/morph-exit") in 0) die "Morph should have failed, but didn't. Unexpected success!" ;; esac We need to check that a workspace creation worked. This requires the directory to exist, and its `.morph` subdirectory to exist, and nothing else. IMPLEMENTS THEN an empty workspace exists is_dir "$DATADIR/workspace" is_dir "$DATADIR/workspace/.morph" assert_equal $(ls -A "$DATADIR/workspace" | wc -l) 1 Tests for things other than `morph init` just want to have a workspace created. IMPLEMENTS GIVEN a workspace run_morph init "$DATADIR/workspace" Implementation sections related to a simulated Trove ---------------------------------------------------- Morph needs access to a Trove, i.e., a git server, in order to do certain kinds of stuff. We simulate this by creating a set of git repositories locally, which we'll tell Morph to access using `file:` URLs. Specifically, we'll create a repository to hold system and stratum morphologies, and another to hold a chunk. IMPLEMENTS GIVEN a git server # Create a directory for all the git repositories. mkdir "$DATADIR/gits" # Create a repo for the system and stratum morphologies. mkdir "$DATADIR/gits/morphs" arch=$(run_morph print-architecture) cat << EOF > "$DATADIR/gits/morphs/test-system.morph" name: test-system kind: system arch: $arch strata: - name: test-stratum repo: test:morphs ref: master morph: test-stratum EOF cat << EOF > "$DATADIR/gits/morphs/test-stratum.morph" name: test-stratum kind: stratum chunks: - name: test-chunk repo: test:test-chunk ref: master morph: test-chunk build-mode: test build-depends: [] EOF run_in "$DATADIR/gits/morphs" git init . run_in "$DATADIR/gits/morphs" git add . run_in "$DATADIR/gits/morphs" git commit -m Initial. run_in "$DATADIR/gits/morphs" git tag -a "test-tag" -m "Tagging test-tag" # Create the chunk repository. mkdir "$DATADIR/gits/test-chunk" cat << EOF > "$DATADIR/gits/test-chunk/test-chunk.morph" name: test-chunk kind: chunk build-system: dummy EOF run_in "$DATADIR/gits/test-chunk" git init . run_in "$DATADIR/gits/test-chunk" git add . run_in "$DATADIR/gits/test-chunk" git commit -m Initial. # Create the Morph configuration file so we can access the repos # using test:foo URL aliases. cat << EOF > "$DATADIR/morph.conf" [config] repo-alias = test=file://$DATADIR/gits/%s#file://$DATADIR/gits/%s cachedir = $DATADIR/cache tempdir = $DATADIR/tmp trove-host= [] EOF mkdir "$DATADIR/cache" mkdir "$DATADIR/tmp" IMPLEMENTS GIVEN a system called (\S+) for architecture (\S+) in the git server cat << EOF > "$DATADIR/gits/morphs/$MATCH_1.morph" arch: $MATCH_2 configuration-extensions: [] description: A system called $MATCH_1 for architectures $MATCH_2 kind: system name: $MATCH_1 strata: - name: test-stratum repo: test:morphs ref: master morph: test-stratum EOF run_in "$DATADIR/gits/morphs" git add "$MATCH_1.morph" run_in "$DATADIR/gits/morphs" git commit -m "Added $MATCH_1 morphology." Morphologies need to support having a null ref, which means look for the stratum in the same repository and ref. Testing this requires different morphologies. IMPLEMENTS GIVEN null refs for local strata nullify_local_refs test:morphs master \ "$DATADIR/gits/morphs/test-system.morph" \ "$DATADIR/gits/morphs/test-stratum.morph" run_in "$DATADIR/gits/morphs" git add . run_in "$DATADIR/gits/morphs" git commit -m "Use null refs." Implementation sections for system branch operations ---------------------------------------------------- Checkout out an existing system branch. We parameterise this so the same phrase can be used to check out any system branch. IMPLEMENTS WHEN the user (attempts to check|checks) out the system (branch|tag) called (\S+) cd "$DATADIR/workspace" set checkout test:morphs "$MATCH_3" if [ $MATCH_1 == "checks" ]; then run_morph "$@" else attempt_morph "$@"; fi Attempt to check out a system branch from a root that has no systems. IMPLEMENTS WHEN the user attempts to check out from a repository with no systems cd "$DATADIR/workspace" attempt_morph checkout test:test-chunk master We also need to verify that a system branch has been checked out. IMPLEMENTS THEN the system branch (\S+) is checked out is_dir "$DATADIR/workspace/$MATCH_1/test:morphs" is_file "$DATADIR/workspace/$MATCH_1/test:morphs/test-system.morph" is_file "$DATADIR/workspace/$MATCH_1/test:morphs/test-stratum.morph" We can create a new branch, off master. IMPLEMENTS WHEN the user (attempts to create|creates) a system branch called (\S+) cd "$DATADIR/workspace" set branch test:morphs "$MATCH_2" if [ $MATCH_1 == "creates"]; then run morph "$@" else attempt_morph "$@"; fi We can create a new branch, off another system branch. IMPLEMENTS WHEN the user creates a system branch called (\S+), based on (\S+) cd "$DATADIR/workspace" run_morph branch test:morphs "$MATCH_1" "$MATCH_2" Attempt to branch a system branch from a root that had no systems. IMPLEMENTS WHEN the user attempts to branch a repository with no systems cd "$DATADIR/workspace" attempt_morph branch test:test-chunk foo Pushing all changes in a system branch checkout to the git server. IMPLEMENTS WHEN the user pushes the system branch called (\S+) to the git server # FIXME: For now, this is just the morphs checkout. run_in "$DATADIR/workspace/$MATCH_1/test:morphs" git push origin HEAD Report workspace path. IMPLEMENTS WHEN the user reports the workspace from the directory (\S+) cd "$DATADIR/workspace/$MATCH_1" run_morph workspace > "$DATADIR/workspace-reported" IMPLEMENTS THEN the workspace is reported correctly assert_equal $(cat "$DATADIR/workspace-reported") "$DATADIR/workspace" IMPLEMENTS WHEN the user attempts to report the workspace from a non-workspace directory cd "$DATADIR" attempt_morph workspace Report system branch name: IMPLEMENTS WHEN the user (attempts to report|reports) the system branch from the directory (\S+) cd "$DATADIR/workspace/$MATCH_2" set $DATADIR/system-branch.reported if [ $MATCH_1 == reports ]; then run_morph show-system-branch > "$@" else attempt_morph show-system-branch > "$@"; fi IMPLEMENTS THEN the system branch is reported as (.*) echo "$MATCH_1" > "$DATADIR/system-branch.actual" diff -u "$DATADIR/system-branch.actual" "$DATADIR/system-branch.reported" Report system branch root repository. IMPLEMENTS WHEN the user (attempts to report|reports) the system branch root repository from the directory (.*) cd "$DATADIR/workspace/$MATCH_2" set $DATADIR/branch-root.reported if [ $MATCH_1 == "reports" ]; then run_morph show-branch-root > "$@" else attempt_morph show-branch-root > "$@"; fi IMPLEMENTS THEN the system branch root repository is reported as (.*) echo "$MATCH_1" > "$DATADIR/branch-root.actual" diff -u "$DATADIR/branch-root.actual" "$DATADIR/branch-root.reported" Editing morphologies with `morph edit`. IMPLEMENTS THEN in branch (\S+), (system|stratum) (\S+) refs (\S+) in (\S+) if [ $MATCH_2 == system ]; then set strata; else set chunks; fi "$SRCDIR/scripts/yaml-extract" \ "$DATADIR/workspace/$MATCH_1/test:morphs/$MATCH_3.morph" \ $@ name="$MATCH_4" ref > "$DATADIR/ref.actual" echo "$MATCH_5" > "$DATADIR/ref.wanted" diff -u "$DATADIR/ref.wanted" "$DATADIR/ref.actual" IMPLEMENTS WHEN the user edits the stratum (\S+) in the system (\S+) in branch (\S+) cd "$DATADIR/workspace/$MATCH_3/test:morphs" run_morph edit "$MATCH_2" "$MATCH_1" IMPLEMENTS WHEN the user edits the chunk (\S+) in the stratum (\S+) in the system (\S+) in branch (\S+) cd "$DATADIR/workspace/$MATCH_4/test:morphs" run_morph edit "$MATCH_3" "$MATCH_2" "$MATCH_1" IMPLEMENTS THEN the edited chunk (\S+) has git branch (\S+) ls -l "$DATADIR/workspace/$MATCH_2" cd "$DATADIR/workspace/$MATCH_2/$MATCH_1" git branch | awk '$1 == "*" { print $2 }' > "$DATADIR/git-branch.actual" echo "$MATCH_2" > "$DATADIR/git-branch.wanted" diff -u "$DATADIR/git-branch.wanted" "$DATADIR/git-branch.actual" IMPLEMENTS WHEN the user edits the chunk (\S+) in the stratum (\S+) with no system specified in branch (\S+) cd "$DATADIR/workspace/$MATCH_3" attempt_morph edit "$MATCH_2" "$MATCH_1" IMPLEMENTS WHEN the user creates an uncommitted system morphology called (\S+) for architecture (\S+) in system branch (\S+) cat << EOF > "$DATADIR/workspace/$MATCH_3/test:morphs/$MATCH_1.morph" arch: $MATCH_2 configuration-extensions: [] description: A system called $MATCH_1 for architectures $MATCH_2 kind: system name: $MATCH_1 strata: - name: test-stratum repo: test:morphs ref: master morph: test-stratum EOF Reporting status of checked out repositories: IMPLEMENTS THEN morph reports no outstanding changes in (\S+) cd "$DATADIR/workspace/$MATCH_1" run_morph status > "$DATADIR/morph.stdout" grep '^No repos have outstanding changes.' "$DATADIR/morph.stdout" IMPLEMENTS THEN morph reports changes in (\S+) in (\S+) only cd "$DATADIR/workspace/$MATCH_1" run_morph status > "$DATADIR/morph.stdout" # morph status is expected to produce records like this: # On branch GITBRANCH, root baserock:baserock/morphs # GITREPO: uncommitted changes # We check thet GITREPO matches $MATCH_2. awk '/: uncommitted changes$/ { print substr($1,1,length($1)-1) }' \ "$DATADIR/morph.stdout" > "$DATADIR/changed.actual" echo "$MATCH_2" > "$DATADIR/changed.wanted" diff -u "$DATADIR/changed.wanted" "$DATADIR/changed.actual" IMPLEMENTS THEN morph reports changes in (\S+) in (\S+) and (\S+) cd "$DATADIR/workspace/$MATCH_1" run_morph status > "$DATADIR/morph.stdout" echo "status morph.stdout:" cat "$DATADIR/morph.stdout" awk '/: uncommitted changes$/ { print substr($1,1,length($1)-1) }' \ "$DATADIR/morph.stdout" | sort > "$DATADIR/changed.actual" (echo "$MATCH_2"; echo "$MATCH_3") | sort > "$DATADIR/changed.wanted" diff -u "$DATADIR/changed.wanted" "$DATADIR/changed.actual" IMPLEMENTS WHEN creating file (\S+) in (\S+) in branch (\S+) touch "$DATADIR/workspace/$MATCH_3/$MATCH_2/$MATCH_1" IMPLEMENTS WHEN adding file (\S+) in (\S+) in branch (\S+) to git cd "$DATADIR/workspace/$MATCH_3/$MATCH_2" git add "$MATCH_1" IMPLEMENTS WHEN committing changes in (\S+) in branch (\S+) cd "$DATADIR/workspace/$MATCH_2/$MATCH_1" git commit -a -m test-commit Running shell command in each checked out repository: IMPLEMENTS WHEN running shell command in each repo in (\S+) cd "$DATADIR/workspace/$MATCH_1" run_morph foreach -- pwd > "$DATADIR/morph.stdout" IMPLEMENTS THEN morph ran command in (\S+) in (\S+) grep -Fx "$MATCH_1" "$DATADIR/morph.stdout" grep -Fx "$DATADIR/workspace/$MATCH_2/$MATCH_1" "$DATADIR/morph.stdout" Petrification and unpetrification: IMPLEMENTS WHEN remembering all refs in (\S+) cd "$DATADIR/workspace/$MATCH_1/test:morphs" list_refs *.morph > "$DATADIR/refs.remembered" IMPLEMENTS THEN (\S+) refs are as remembered cd "$DATADIR/workspace/$MATCH_1/test:morphs" # FIXME: petrify/unpetrify doesn't work quite right at this time: # petrify can change a ref to a stratum to point at the system # branch, but does it without adding an unpetrify-ref, and so # unpetrify doesn't undo the change. We ignore this bug for the # time being, in order to make the test suite pass. When the # petrification code has been cleaned up to not be so hairy, # we'll fix the test and the code. # # We would like to verify the result like this: # # list_refs *.morph > "$DATADIR/refs.now" # diff -u "$DATADIR/refs.remembered" "$DATADIR/refs.now" # # However, due to the bug, we have to do it in a more complicated # manner. list_refs *.morph | while read filename ref do orig=$(awk -v "f=$filename" '$1 == f { print $2 }' \ "$DATADIR/refs.remembered") if [ "$orig" != "$ref" ] && [ "$ref" != "$MATCH_1" ] then die "Un-petrified ref: $filename $ref (should be $orig)" fi done IMPLEMENTS WHEN petrifying (\S+) cd "$DATADIR/workspace/$MATCH_1/test:morphs" run_morph petrify IMPLEMENTS WHEN unpetrifying (\S+) cd "$DATADIR/workspace/$MATCH_1/test:morphs" run_morph unpetrify IMPLEMENTS THEN (\S+) is petrified cd "$DATADIR/workspace/$MATCH_1/test:morphs" assert_morphologies_are_petrified "$MATCH_1" *.morph IMPLEMENTS THEN (\S+) is not petrified cd "$DATADIR/workspace/$MATCH_1/test:morphs" ! assert_morphologies_are_petrified "$MATCH_1" *.morph Tagging. IMPLEMENTS WHEN the user tags the system branch called (\S+) as (\S+) cd "$DATADIR/workspace/$MATCH_1/test:morphs" set tag "$MATCH_2" -- -m "testing morph tagging" run_morph tag "$MATCH_2" -- -m "testing morph tagging" IMPLEMENTS WHEN the user attempts to tag the system branch called (\S+) as (\S+) cd "$DATADIR/workspace/$MATCH_1/test:morphs" attempt_morph tag "$MATCH_2" -- -m "testing morph tagging" IMPLEMENTS THEN morph tag (\S+) in (\S+) is an annotated git tag cd "$DATADIR/workspace/$MATCH_2/test:morphs" if git show "$MATCH_1" | head -n1 | grep -v '^tag ' then die "git tag $MATCH_1 is not an annotated tag" fi IMPLEMENTS THEN morph tag (\S+) in (\S+) refers to a petrified commit cd "$DATADIR/workspace/$MATCH_2/test:morphs" git ls-tree "$MATCH_1" | awk '$NF ~ /\.morph$/ { print $NF }' | while read x do git cat-file blob "$MATCH_1:$x" > temptemptemp assert_morphologies_are_petrified "$MATCH_1" temptemptemp done Generating a manifest. IMPLEMENTS GIVEN a system artifact mkdir "$DATADIR/hello_world" git init "$DATADIR/hello_world" touch "$DATADIR/hello_world/configure.ac" run_in "$DATADIR/hello_world" git add configure.ac run_in "$DATADIR/hello_world" git commit -m 'Add configure.ac' mkdir "$DATADIR/baserock" run_in "$DATADIR/hello_world" cat << EOF \ > "$DATADIR/baserock/hello_world.meta" { "artifact-name": "hello_world", "cache-key": "ab8d00a80298a842446ce23507cea6b4d0e34c7ddfa05c67f460318b04d21308", "kind": "chunk", "morphology": "hello_world.morph", "original_ref": "$(run_in "$DATADIR/hello_world" git rev-parse HEAD)", "repo": "file://$DATADIR/hello_world", "repo-alias": "upstream:hello_world", "sha1": "$(run_in "$DATADIR/hello_world" git rev-parse HEAD)", "source-name": "hello_world" } EOF run_in "$DATADIR" tar -c baserock > "$DATADIR/artifact.tar" IMPLEMENTS WHEN morph generates a manifest run_morph generate-manifest "$DATADIR/artifact.tar" > "$DATADIR/manifest" IMPLEMENTS THEN the manifest is generated # Generated manifest should contain the name of the repository if ! grep -q hello_world "$DATADIR/manifest"; then die "Output isn't what we expect" fi Implementation sections for building ==================================== IMPLEMENTS WHEN the user (attempts to build|builds) the system (\S+) in branch (\S+) cd "$DATADIR/workspace/$MATCH_3" set build "$MATCH_2" if [ $MATCH_1 == "builds" ]; then run_morph "$@" else attempt_morph "$@"; fi Implementation sections for deployment ====================================== IMPLEMENTS WHEN the user (attempts to deploy|deploys) the (system|cluster) (\S+) in branch (\S+) cd "$DATADIR/workspace/$MATCH_4" set build "$MATCH_3" if [ $MATCH_1 == "deploys" ]; then run_morph "$@" else attempt_morph deploy "$MATCH_3"; fi Implementations sections for reading error messages =================================================== IMPLEMENTS THEN the (branch|build|checkout|deploy|edit|init) error message includes the string "(.*)" grep "$MATCH_2" "$DATADIR/result-$MATCH_1" IMPLEMENTS for test file and directory handling =============================================== The IMPLEMENTS sections in this chapter create files and directories for use as test data, and set and test their contents and permissions and ownerships. Create a directory ------------------ IMPLEMENTS GIVEN a directory called (\S+) mkdir -p "$DATADIR/$MATCH_1" Create a file ------------- The file contents is used as a `printf`(1) format string. IMPLEMENTS GIVEN a file called (\S+) containing "(.*)" printf "$MATCH_2" > "$DATADIR/$MATCH_1" Set attributes on a file or directory ------------------------------------- IMPLEMENTS GIVEN (\S+) is owned by uid (\S+) chown "$MATCH_2" "$DATADIR/$MATCH_1" IMPLEMENTS GIVEN (\S+) is owned by gid (\S+) chgrp "$MATCH_2" "$DATADIR/$MATCH_1" IMPLEMENTS GIVEN (\S+) has permissions (\S+) chmod "$MATCH_2" "$DATADIR/$MATCH_1" Check attributes of a file on the filesystem -------------------------------------------- IMPLEMENTS THEN file (\S+) exists test -e "$DATADIR/$MATCH_1" IMPLEMENTS THEN file (\S+) has permissions (\S+) stat -c %A "$DATADIR/$MATCH_1" | grep -Fx -e "$MATCH_2" IMPLEMENTS THEN file (\S+) is owned by uid (\d+) stat -c %u "$DATADIR/$MATCH_1" | grep -Fx -e "$MATCH_2" IMPLEMENTS THEN file (\S+) is owned by gid (\d+) stat -c %g "$DATADIR/$MATCH_1" | grep -Fx -e "$MATCH_2" IMPLEMENTS THEN file (\S+) is empty stat -c %s "$DATADIR/$MATCH_1" | grep -Fx 0 Check contents of a file ------------------------ We treat the contents of the file in the step as a `printf`(1) format string, to allow newlines and other such stuff to be expressed. IMPLEMENTS THEN file (\S+) contains "(.*)" printf "$MATCH_2" | diff - "$DATADIR/$MATCH_1" IMPLEMENTS for running programs =============================== This chapter contains IMPLEMENTS sections for running programs. It is currently a bit of a placeholder. Remember environment variables to set when running -------------------------------------------------- We need to manage the environment. We store the extra environment variables in `$DATADIR/env`. We treat the value as a format string for `printf`(1) so that newlines etc can be used. IMPLEMENTS GIVEN an environment variable (\S+) containing "(.*)" printf "export $MATCH_1=$MATCH_2" >> "$DATADIR/env" Implementations for building systems ------------------------------------ IMPLEMENTS THEN morph build the system (\S+) of the (branch|tag) (\S+) of the repo (\S+) cd "$DATADIR/workspace/$MATCH_3/$MATCH_4" run_morph build "$MATCH_1"