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 IMPLEMENTS THEN morph succeeded case $(cat "$DATADIR/morph-exit") in 0) echo "Morph succeeded!" ;; *) cat "$DATADIR/result-latest" >&2 die "Morph should have succeeded, but didn't. Unexpected failure!" ;; 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 the bootstrap chunk repositories mkdir "$DATADIR/gits/bootstrap-chunk" cd "$DATADIR/gits/bootstrap-chunk" git init . git checkout -b bootstrap cp "$SRCDIR/scripts/test-shell.c" sh.c install /dev/stdin <<'EOF' configure #!/bin/true EOF printf >Makefile ' CFLAGS = -D_GNU_SOURCE -static all: sh install: sh \tinstall -D -m755 sh $(DESTDIR)/bin/sh' git add . git commit -m "Add bootstrap shell" git checkout --orphan master HEAD # Commit a pre-built test-shell, as a compiler is too heavy to bootstrap make sh mkdir bin mv sh bin/sh git rm -f Makefile sh.c configure git add bin/sh git commit -m "Build bootstrap shell with bootstrap shell" # Create the test chunk repository. mkdir "$DATADIR/gits/test-chunk" cd "$DATADIR/gits/test-chunk" git init . # To verify that chunk splitting works, we have a chunk that installs # dummy files in all the places that different kinds of files are # usually installed. e.g. executables in `/bin` and `/usr/bin` PREFIX=/usr DESTDIR=. # It's important that we can test whether executables get # installed, so we install an empty script into `/usr/bin/test` and # `/usr/sbin/test`. # `install -D` will create the leading components for us, and install # defaults to creating the file with its executable bit set. # `install` needs a source file to install, but since we only care # that the file exists, rather than its contents, we can use /dev/null # as the source. for bindir in bin sbin; do install -D /dev/null "$DESTDIR/$PREFIX/$bindir/test" done # We need shared libraries too, sometimes they're libraries to support # the executables that a chunk provides, sometimes for other chunks. # Libraries can be found in a variety of places, hence why we install # them into lib, lib32 and lib64. # Shared libraries' file names start with lib and end with `.so` # for shared-object, with version numbers optionally suffixed. for libdir in lib lib32 lib64; do dirpath="$DESTDIR/$PREFIX/$libdir" install -D /dev/null "$dirpath/libtest.so" ln -s libtest.so "$dirpath/libtest.so.0" ln -s libtest.so.0 "$dirpath/libtest.so.0.0" ln -s libtest.so.0.0 "$dirpath/libtest.so.0.0.0" done # Shared objects aren't the only kind of library, some executable # binaries count as libraries, such as git's plumbing commands. # In some distributions they go into /lib, in others, and the default # autotools configuration, they go into /libexec. install -D /dev/stdin "$DESTDIR/$PREFIX/libexec/test-bin" <<'EOF' #!/bin/sh echo Hello World EOF # As well as run-time libraries, there's development files. For C # this is headers, which describe the API of the libraries, which # then use the shared objects, and other files which are needed # to build the executables, but aren't needed to run them, such as # static libraries. # Header files go into `include` and end with `.h`. They are not # executable, so the install command changes the permissions with the # `-m` option. install -D -m 644 /dev/stdin <<'EOF' "$DESTDIR/$PREFIX/include/test.h" int foo(void); EOF # `pkg-config` is a standard way to locate libraries and get the # compiler flags needed to build with the library. It's also used # for other configuration for packages that don't install binaries, # so as well as being found in `lib/pkgconfig`, it can be found in # `share/pkgconfig`, so we install dummy files to both. for pkgdir in lib lib32 lib64 share; do install -D -m 644 /dev/stdin < "$DATADIR/morph.conf" [config] repo-alias = test=git://127.0.0.1:$GIT_DAEMON_PORT/%s#git://127.0.0.1:$GIT_DAEMON_PORT/%s cachedir = $DATADIR/cache tempdir = $DATADIR/tmp trove-host= [] EOF mkdir "$DATADIR/cache" mkdir "$DATADIR/tmp" Some resources are cleaned up by yarn, forked processes aren't one of these, so need to shut down the git daemon after we finish. IMPLEMENTS FINALLY the git server is shut down pid_file="$DATADIR/git-daemon-pid" if [ -e "$pid_file" ]; then start-stop-daemon --stop --pidfile "$pid_file" --oknodo fi We need a consistent value for the architecture in some tests, so we have a morphology using the test architecture. IMPLEMENTS GIVEN a system called (\S+) for the test architecture in the git server name="$(basename "${MATCH_1%.*}")" cat << EOF > "$DATADIR/gits/morphs/$MATCH_1" arch: testarch configuration-extensions: [] description: A system called $name for test architecture kind: system name: $name strata: - name: build-essential morph: strata/build-essential.morph - name: core morph: strata/core.morph EOF run_in "$DATADIR/gits/morphs" git add "strata/build-essential.morph" run_in "$DATADIR/gits/morphs" git add "strata/core.morph" run_in "$DATADIR/gits/morphs" git add "$MATCH_1" run_in "$DATADIR/gits/morphs" git commit -m "Added $MATCH_1 and strata morphologies." You need an architecture to build a system, we don't default to the host architecture. IMPLEMENTS GIVEN a system called (\S+) with no architecture in the git server name="$(basename "${MATCH_1%.*}")" cat << EOF > "$DATADIR/gits/morphs/$MATCH_1" configuration-extensions: [] description: A system called $name for test architecture kind: system name: $name strata: - name: build-essential morph: strata/build-essential.morph - name: core morph: strata/core.morph EOF run_in "$DATADIR/gits/morphs" git add "$MATCH_1" run_in "$DATADIR/gits/morphs" git commit -m "Added $MATCH_1." 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 IMPLEMENTS WHEN the user attempts to check out the system branch from (\S+) called (\S+) cd "$DATADIR/workspace" attempt_morph branch-from-image --metadata-dir "$DATADIR/$MATCH_1/baserock" "$MATCH_2" 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/systems/test-system.morph" is_file "$DATADIR/workspace/$MATCH_1/test/morphs/strata/core.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 cd "$DATADIR/workspace/$MATCH_1/" run_morph foreach -- sh -c 'git push -u origin HEAD 2>&1' 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 "$DATADIR/$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+), stratum (\S+) refs (\S+) in (\S+) "$SRCDIR/scripts/yaml-extract" \ "$DATADIR/workspace/$MATCH_1/test/morphs/$MATCH_2" \ chunks name="$MATCH_3" ref > "$DATADIR/ref.actual" echo "$MATCH_4" > "$DATADIR/ref.wanted" diff -u "$DATADIR/ref.wanted" "$DATADIR/ref.actual" IMPLEMENTS THEN in branch (\S+), (system|stratum) (\S+) refers to (\S+) without (\S+) if [ $MATCH_2 == system ]; then field=strata; else field=build-depends; fi "$SRCDIR/scripts/yaml-extract" \ "$DATADIR/workspace/$MATCH_1/test/morphs/$MATCH_3" \ "$field" name="$MATCH_4" "$MATCH_5" 2>&1 | grep -qFe "Object does not contain $MATCH_5" IMPLEMENTS WHEN the user edits the chunk (\S+) in branch (\S+) cd "$DATADIR/workspace/$MATCH_2/test/morphs" run_morph edit "$MATCH_1" IMPLEMENTS THEN the edited chunk (\S+) has git branch (\S+) ls -l "$DATADIR/workspace/$MATCH_2" chunkdir="$(slashify_colons "$MATCH_1")" cd "$DATADIR/workspace/$MATCH_2/$chunkdir" git rev-parse --abbrev-ref HEAD > "$DATADIR/git-branch.actual" echo "$MATCH_2" > "$DATADIR/git-branch.wanted" diff -u "$DATADIR/git-branch.wanted" "$DATADIR/git-branch.actual" Cloning chunk repos with `morph get-repo`. IMPLEMENTS WHEN the user gets the repo for chunk (\S+) in branch (\S+) cd "$DATADIR/workspace/$MATCH_2/test/morphs" run_morph get-repo "$MATCH_1" IMPLEMENTS WHEN the user (gets|attempts to get) the repo for chunk (\S+) from branch (\S+) in directory (\S+) cd "$DATADIR/workspace/$MATCH_3/test/morphs" if [ $MATCH_1 == gets ]; then run_morph get-repo "$MATCH_2" "$DATADIR/$MATCH_3"; else attempt_morph get-repo "$MATCH_2" "$DATADIR/$MATCH_3"; fi IMPLEMENTS WHEN the user gets the repo for chunk (\S+) from branch (\S+) in directory (\S+) at ref (\S+) cd "$DATADIR/workspace/$MATCH_2/test/morphs" run_morph get-repo "$MATCH_1" "$DATADIR/$MATCH_3" --ref "$MATCH_4" IMPLEMENTS THEN the repo for chunk (\S+) was cloned into the system branch (\S+) if [[ -d "$DATADIR/workspace/$MATCH_2/test/$MATCH_1" ]]; then exit 0 fi exit 1 IMPLEMENTS THEN the repo for chunk (\S+) was cloned into the directory (\S+) if [[ -d "$DATADIR/$MATCH_2" ]]; then exit 0 fi exit 1 IMPLEMENTS THEN the repo in directory (\S+) has HEAD (\S+) cd "$DATADIR/$MATCH_1" if [ "$MATCH_2" == `git rev-parse --abbrev-ref HEAD` ]; then exit 0 fi exit 1 To produce buildable morphologies, we need them to be of the same architecture as the machine doing the testing. This uses `morph print-architecture` to get a value appropriate for morph. IMPLEMENTS WHEN the user creates an uncommitted system morphology called (\S+) for our architecture in system branch (\S+) arch=$(run_morph print-architecture) name="$(basename "${MATCH_1%.*}")" install -m644 -D /dev/stdin << EOF "$DATADIR/workspace/$MATCH_2/test/morphs/$MATCH_1" arch: $arch configuration-extensions: [] description: A system called $name for architectures $arch kind: system name: $name strata: - name: build-essential morph: strata/build-essential.morph - name: core morph: strata/core.morph 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 chunkdir="$(slashify_colons "$MATCH_2")" cd "$DATADIR/workspace/$MATCH_3/$chunkdir" git add "$MATCH_1" IMPLEMENTS WHEN committing changes in (\S+) in branch (\S+) cd "$DATADIR/workspace/$MATCH_2/$(slashify_colons "$MATCH_1")" git commit -a -m test-commit 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 Implementations for temporary build branch handling --------------------------------------------------- IMPLEMENTS GIVEN the workspace contains no temporary build branches build_ref_prefix=baserock/builds/ cd "$DATADIR/workspace" # Want to use -execdir here, but busybox find doesn't support it find . -name .git -print | while read gitdir; do ( cd "$(dirname "$gitdir")" eval "$(git for-each-ref --shell \ --format='git update-ref -d %(refname) %(objectname)' \ "refs/heads/$build_ref_prefix")" ); done IMPLEMENTS GIVEN the git server contains no temporary build branches build_ref_prefix=refs/heads/baserock/builds/ cd "$DATADIR/gits" # Want to use -execdir here, but busybox find doesn't support it find . -name .git -print | while read gitdir; do ( cd "$(dirname "$gitdir")" eval "$(git for-each-ref --shell \ --format='git update-ref -d %(refname) %(objectname)' \ "$build_ref_prefix")" git config receive.denyCurrentBranch ignore rm -f .git/morph-pushed-branches mkdir -p .git/hooks cat >.git/hooks/post-receive <<'EOF' #!/bin/sh touch "$GIT_DIR/hook-ever-run" exec cat >>"$GIT_DIR/morph-pushed-branches" EOF chmod +x .git/hooks/post-receive ); done IMPLEMENTS GIVEN we can build with local branches sed -i -e '/push-build-branches/d' "$DATADIR/morph.conf" IMPLEMENTS GIVEN we must build from pushed branches cat >>"$DATADIR/morph.conf" <<'EOF' push-build-branches = True EOF IMPLEMENTS THEN the (\S+) repository in the workspace for (\S+) has temporary build branches build_ref_prefix=refs/heads/baserock/builds/ cd "$DATADIR/workspace/$MATCH_2/$(slashify_colons "test:$MATCH_1")" git for-each-ref | grep -F "$build_ref_prefix" IMPLEMENTS THEN the (\S+) repository in the workspace for (\S+) has no temporary build branches build_ref_prefix=refs/heads/baserock/builds/ cd "$DATADIR/workspace/$MATCH_2/$(slashify_colons "test:$MATCH_1")" if git for-each-ref | grep -F "$build_ref_prefix"; then die Did not expect repo to contain build branches fi IMPLEMENTS THEN no temporary build branches were pushed to the (\S+) repository build_ref_prefix=refs/heads/baserock/builds/ cd "$DATADIR/gits/$MATCH_1/.git" if test -e morph-pushed-branches && grep -F "$build_ref_prefix" morph-pushed-branches; then die Did not expect any pushed build branches fi IMPLEMENTS THEN temporary build branches were pushed to the (\S+) repository build_ref_prefix=refs/heads/baserock/builds/ cd "$DATADIR/gits/$MATCH_1/.git" test -e morph-pushed-branches && grep -F "$build_ref_prefix" morph-pushed-branches Implementation sections for building ==================================== IMPLEMENTS WHEN the user (attempts to )?((dist)?build)s? the system (\S+) in branch (\S+) cd "$DATADIR/workspace/$MATCH_5/test/morphs" set "$MATCH_2" "$MATCH_4" if [ "$MATCH_1" != "attempts to " ]; then run_morph "$@" else attempt_morph "$@"; fi IMPLEMENTS WHEN the user (attempts to )?((dist)?build)s? (\S+) from the system (\S+) in branch (\S+) cd "$DATADIR/workspace/$MATCH_6/test/morphs" set "$MATCH_2" "$MATCH_5" "$MATCH_4" if [ "$MATCH_1" != "attempts to " ]; then run_morph "$@" else attempt_morph "$@"; fi Implementation sections for cross-bootstraping ============================================== IMPLEMENTS THEN the user cross-bootstraps the system (\S+) in branch (\S+) of repo (\S+) to the arch (\S+) cd "$DATADIR/workspace/$MATCH_2/test/morphs" set -- cross-bootstrap "$MATCH_4" "$MATCH_3" "$MATCH_2" "$MATCH_1" run_morph "$@" Implementation sections for deployment ====================================== Defaults are set in the cluster morphology, so we can deploy without setting any extra parameters, but we also need to be able to override them, so they can be added to the end of the implements section. IMPLEMENTS WHEN (from directory (\S+) )?the user (attempts to deploy|deploys) the (system|cluster) (using the absolute path to )?(\S+) in branch (\S+)( with options (.*))? if [ "$MATCH_1" != "" ] then cd "$DATADIR/$MATCH_2" else cd "$DATADIR/workspace/$MATCH_7/test/morphs" fi if [ "$MATCH_5" != "" ] then set -- deploy "$(pwd)/$MATCH_6" else set -- deploy "$MATCH_6" fi if [ "$MATCH_8" != '' ]; then # eval used so word splitting in the text is preserved eval set -- '"$@"' $MATCH_9 fi if [ "$MATCH_3" = "deploys" ]; then run_morph "$@" else attempt_morph "$@"; fi IMPLEMENTS WHEN the user (attempts to deploy|deploys) (.*) from cluster (\S+) in branch (\S+) cd "$DATADIR/workspace/$MATCH_4/test/morphs" set -- deploy "$MATCH_3" systems=$(echo "$MATCH_2" | sed -e 's/, /\n/g' -e 's/ and /\n/g') set -- "$@" $systems if [ "$MATCH_1" = "deploys" ]; then run_morph "$@" else attempt_morph "$@"; fi IMPLEMENTS WHEN the user (attempts to upgrade|upgrades) the (system|cluster) (\S+) in branch (\S+)( with options (.*))? cd "$DATADIR/workspace/$MATCH_4/test/morphs" set -- upgrade "$MATCH_3" if [ "$MATCH_5" != '' ]; then # eval used so word splitting in the text is preserved eval set -- '"$@"' $MATCH_6 fi if [ "$MATCH_1" = "upgrades" ]; then run_morph "$@" else attempt_morph "$@"; 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" Remove a file ------------- IMPLEMENTS GIVEN the file(s)? (.*) (is|are) removed cd "$DATADIR" files=$(echo "$MATCH_2" | sed -e 's/, /\n/g' -e 's/ and /\n/g') rm $files 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+) does not exist 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 IMPLEMENTS THEN file (\S+) matches (.*) grep -q "$MATCH_2" "$DATADIR/$MATCH_1" Disk image manipulation ----------------------- We need to test disk images we create. In the absence of tools for inspecting disks without mounting them, we need commands to handle this. IMPLEMENTS WHEN disk image (\S+) is mounted at (.*) mkdir -p "$DATADIR/$MATCH_2" mount -o loop "$DATADIR/$MATCH_1" "$DATADIR/$MATCH_2" IMPLEMENTS FINALLY (\S+) is unmounted umount -d "$DATADIR/$MATCH_1" We may not have enough space to run some tests that have disk images. IMPLEMENTS ASSUMING there is space for (\d+) (\d+)(\S*) disk images? # Count is included as an argument, so that if we change the disk # image sizes then it's more obvious when we need to change the # assumption, since it's the same value. count="$MATCH_1" case "$MATCH_3" in '') size="$MATCH_2" ;; M) size=$(expr "$MATCH_2" '*' 1024 '*' 1024 ) ;; G) size=$(expr "$MATCH_2" '*' 1024 '*' 1024 '*' 1024 ) ;; *) echo Unrecognized size suffix: "$MATCH_3" >&2 exit 1 esac total_image_size="$(expr "$size" '*' "$count" )" blocks="$(stat -f -c %a "$DATADIR")" block_size="$(stat -f -c %S "$DATADIR")" disk_free=$(expr "$blocks" '*' "$block_size" ) test "$disk_free" -gt "$total_image_size" 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 ((dist)?build) the system (\S+) of the (branch|tag) (\S+) cd "$DATADIR/workspace/$MATCH_5/test/morphs" run_morph "$MATCH_1" "$MATCH_3" IMPLEMENTS WHEN the user builds (\S+) of the (\S+) (branch|tag) cd "$DATADIR/workspace/$MATCH_2/test/morphs" run_morph build "$MATCH_1" IMPLEMENTS WHEN from the directory (\S+) the user attempts to morph build the system (\S+) cd "$DATADIR/$MATCH_1" attempt_morph build "$MATCH_2" IMPLEMENTS WHEN from the directory (\S+) the user attempts to morph build the system using the absolute path to (\S+) cd "$DATADIR/$MATCH_1" attempt_morph build "$(pwd)/$MATCH_2" Implementations for tarball inspection -------------------------------------- IMPLEMENTS THEN tarball (\S+) contains (.*) tar -tf "$DATADIR/$MATCH_1" | grep -Fe "$MATCH_2" IMPLEMENTS THEN tarball (\S+) doesn't contain (.*) ! tar -tf "$DATADIR/$MATCH_1" | grep -Fe "$MATCH_2" Implementations for morphology manipulation ========================================== Altering morphologies in their source repositories -------------------------------------------------- IMPLEMENTS GIVEN system (\S+) uses (.+) from (\S+) "$SRCDIR/scripts/edit-morph" set-system-artifact-depends \ "$DATADIR/gits/morphs/$MATCH_1" "$MATCH_3" "$MATCH_2" run_in "$DATADIR/gits/morphs" git add "$MATCH_1" run_in "$DATADIR/gits/morphs" git commit -m "Make $MATCH_1 only use $MATCH_2" IMPLEMENTS GIVEN stratum (\S+) has match rules: (.*) cd "$DATADIR/gits/morphs" "$SRCDIR/scripts/edit-morph" set-stratum-match-rules \ "$MATCH_1" "$MATCH_2" git add "$MATCH_1" git commit -m "Make $MATCH_1 match $MATCH_2" Altering morphologies in the workspace -------------------------------------- ### Altering strata ### IMPLEMENTS GIVEN stratum (\S+) in system branch (\S+) has match rules: (.*) cd "$DATADIR/workspace/$MATCH_2/test/morphs" "$SRCDIR/scripts/edit-morph" set-stratum-match-rules \ "$MATCH_1" "$MATCH_3" ### Altering clusters ### IMPLEMENTS GIVEN a cluster called (\S+) in system branch (\S+) name="$MATCH_1" branch="$MATCH_2" "$SRCDIR/scripts/edit-morph" cluster-init \ "$DATADIR/workspace/$branch/test/morphs/$name" IMPLEMENTS GIVEN a (sub)?system in cluster (\S+) in branch (\S+) called (\S+) cluster="$MATCH_2" branch="$MATCH_3" name="$MATCH_4" "$SRCDIR/scripts/edit-morph" cluster-system-init \ "$DATADIR/workspace/$branch/test/morphs/$cluster" "$name" IMPLEMENTS GIVEN (sub)?system (\S+) in cluster (\S+) in branch (\S+) builds (\S+) name="$MATCH_2" cluster="$MATCH_3" branch="$MATCH_4" morphology="$MATCH_5" "$SRCDIR/scripts/edit-morph" cluster-system-set-morphology \ "$DATADIR/workspace/$branch/test/morphs/$cluster" "$name" \ "$morphology" IMPLEMENTS GIVEN (sub)?system (\S+) in cluster (\S+) in branch (\S+) has deployment type: (\S+) name="$MATCH_2" cluster="$MATCH_3" branch="$MATCH_4" type="$MATCH_5" "$SRCDIR/scripts/edit-morph" cluster-system-set-deploy-type \ "$DATADIR/workspace/$branch/test/morphs/$cluster" "$name" \ "$type" IMPLEMENTS GIVEN (sub)?system (\S+) in cluster (\S+) in branch (\S+) has deployment location: (\S+) name="$MATCH_2" cluster="$MATCH_3" branch="$MATCH_4" location="$MATCH_5" "$SRCDIR/scripts/edit-morph" cluster-system-set-deploy-location \ "$DATADIR/workspace/$branch/test/morphs/$cluster" "$name" \ "$location" IMPLEMENTS GIVEN (sub)?system (\S+) in cluster (\S+) in branch (\S+) has deployment variable: ([^=]+)=(.*) name="$MATCH_2" cluster="$MATCH_3" branch="$MATCH_4" key="$MATCH_5" val="$MATCH_6" "$SRCDIR/scripts/edit-morph" cluster-system-set-deploy-variable \ "$DATADIR/workspace/$branch/test/morphs/$cluster" "$name" \ "$key" "$val" Distbuild ========= IMPLEMENTS ASSUMING the morph-cache-server can be run "$SRCDIR/morph-cache-server" --version IMPLEMENTS GIVEN a communal cache server # The communal cache server has direct access to the git repositories # and can have artifacts placed on it artifact_dir="$DATADIR/shared-artifacts" mkdir -p "$artifact_dir" read_cache_server_port_file="$DATADIR/read-cache-server-port" read_cache_server_pid_file="$DATADIR/read-cache-server-pid" start_cache_server "$read_cache_server_port_file" \ "$read_cache_server_pid_file" \ "$artifact_dir" write_cache_server_port_file="$DATADIR/write-cache-server-port" write_cache_server_pid_file="$DATADIR/write-cache-server-pid" start_cache_server "$write_cache_server_port_file" \ "$write_cache_server_pid_file" \ "$artifact_dir" --enable-writes IMPLEMENTS FINALLY the communal cache server is terminated stop_daemon "$DATADIR/read-cache-server-pid" stop_daemon "$DATADIR/write-cache-server-pid" IMPLEMENTS GIVEN a distbuild worker # start worker cache server, so other workers can download results worker_cachedir="$DATADIR/distbuild-worker-cache" worker_artifacts="$worker_cachedir/artifacts" mkdir -p "$worker_artifacts" worker_cache_port_file="$DATADIR/worker-cache-server-port" worker_cache_pid_file="$DATADIR/worker-cache-server-pid" start_cache_server "$worker_cache_port_file" \ "$worker_cache_pid_file" \ "$worker_artifacts" # start worker daemon worker_daemon_port_file="$DATADIR/worker-daemon-port" worker_daemon_pid_file="$DATADIR/worker-daemon-pid" mkfifo "$worker_daemon_port_file" communal_cache_port="$(cat "$DATADIR/read-cache-server-port")" start-stop-daemon --start --pidfile="$worker_daemon_pid_file" \ --background --make-pidfile --verbose \ --startas="$SRCDIR/morph" -- worker-daemon \ --no-default-configs --config "$DATADIR/morph.conf" \ --worker-daemon-port=0 \ --worker-daemon-port-file="$worker_daemon_port_file" \ --cachedir="$worker_cachedir" \ --artifact-cache-server="http://localhost:$communal_cache_port/" \ --git-resolve-cache-server="http://localhost:$communal_cache_port/" \ --log="$DATADIR/worker-daemon-log" \ >"$DATADIR/worker-daemon-out" 2>"$DATADIR/worker-daemon-err" worker_daemon_port="$(cat "$worker_daemon_port_file")" rm "$worker_daemon_port_file" echo "$worker_daemon_port" >"$worker_daemon_port_file" # start worker helper helper_pid_file="$DATADIR/worker-daemon-helper-pid" start-stop-daemon --start --pidfile="$helper_pid_file" \ --background --make-pidfile --verbose \ --startas="$SRCDIR/distbuild-helper" -- \ --no-default-configs \ --parent-port="$worker_daemon_port" # set up builder config install /dev/stdin <<'EOF' "$DATADIR/morph" #!/bin/sh exec "$SRCDIR/morph" --quiet \ --cachedir-min-space=0 --tempdir-min-space=0 \ --no-default-config --config "$DATADIR/morph.conf" \ --cachedir "$DATADIR/distbuild-worker-cache" "$@" EOF IMPLEMENTS FINALLY the distbuild worker is terminated stop_daemon "$DATADIR/worker-cache-server-pid" stop_daemon "$DATADIR/worker-daemon-pid" stop_daemon "$DATADIR/worker-daemon-helper-pid" IMPLEMENTS GIVEN a distbuild controller worker_cache_port_file="$DATADIR/worker-cache-server-port" worker_cache_server_port="$(cat "$worker_cache_port_file")" worker_daemon_port_file="$DATADIR/worker-daemon-port" worker_daemon_port="$(cat "$worker_daemon_port_file")" communal_cache_read_port="$(cat "$DATADIR/read-cache-server-port")" communal_cache_write_port="$(cat "$DATADIR/write-cache-server-port")" initiator_port_file="$DATADIR/controller-initiator-port" mkfifo "$initiator_port_file" helper_port_file="$DATADIR/controller-helper-port" mkfifo "$helper_port_file" controller_pid_file="$DATADIR/controller-pid" start-stop-daemon --start --pidfile="$controller_pid_file" \ --background --make-pidfile --verbose \ --startas="$SRCDIR/morph" -- controller-daemon \ --no-default-configs --config "$DATADIR/morph.conf" \ --worker="localhost:$worker_daemon_port" \ --worker-cache-server-port="$worker_cache_server_port" \ --artifact-cache-server="http://localhost:$communal_cache_read_port/" \ --git-resolve-cache-server="http://localhost:$communal_cache_read_port/" \ --writeable-cache-server="http://localhost:$communal_cache_write_port/" \ --controller-helper-port=0 \ --controller-helper-port-file="$helper_port_file" \ --controller-initiator-port=0 \ --controller-initiator-port-file="$initiator_port_file" \ --morph-instance="$DATADIR/morph" \ --log="$DATADIR/controller-daemon-log" \ >"$DATADIR/controller-daemon-out" 2>"$DATADIR/controller-daemon-err" helper_port="$(cat "$helper_port_file")" rm "$helper_port_file" echo "$helper_port" >"$helper_port_file" initiator_port="$(cat "$initiator_port_file")" rm "$initiator_port_file" echo "$initiator_port" >"$initiator_port_file" # start worker helper helper_pid_file="$DATADIR/controller-helper-pid" start-stop-daemon --start --pidfile="$helper_pid_file" \ --background --make-pidfile --verbose \ --startas="$SRCDIR/distbuild-helper" -- \ --no-default-configs \ --parent-port="$helper_port" # make builds use distbuild echo "controller-initiator-port = $initiator_port" \ >>"$DATADIR/morph.conf" echo "controller-initiator-address = localhost" \ >>"$DATADIR/morph.conf" echo "initiator-step-output-dir = $DATADIR" \ >>"$DATADIR/morph.conf" IMPLEMENTS FINALLY the distbuild controller is terminated stop_daemon "$DATADIR/controller-helper-pid" stop_daemon "$DATADIR/controller-pid"