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!" ;; *) 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 chunk repository. mkdir "$DATADIR/gits/test-chunk" run_in "$DATADIR/gits/test-chunk" git init . cat > "$DATADIR/gits/test-chunk/test-bin" <<'EOF' #!/bin/sh echo Hello World EOF cat > "$DATADIR/gits/test-chunk/test.h" <<'EOF' int foo(void); EOF cat > "$DATADIR/gits/test-chunk/test.pc" <<'EOF' prefix=/usr includedir=${prefix}/include Name: test Cflags: -I{includedir} EOF run_in "$DATADIR/gits/test-chunk" git add . run_in "$DATADIR/gits/test-chunk" git commit --allow-empty -m Initial. # Create a repo for the morphologies. mkdir "$DATADIR/gits/morphs" arch=$(run_morph print-architecture) install -m644 -D /dev/stdin << EOF "$DATADIR/gits/morphs/systems/test-system.morph" name: test-system kind: system arch: $arch strata: - name: test-stratum morph: strata/test-stratum.morph EOF install -m644 -D /dev/stdin << EOF "$DATADIR/gits/morphs/strata/test-stratum.morph" name: test-stratum kind: stratum chunks: - name: test-chunk repo: test:test-chunk morph: test-chunk.morph unpetrify-ref: master ref: $(run_in "$DATADIR/gits/test-chunk" git rev-parse master) build-mode: test build-depends: [] EOF # 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` install -m644 -D /dev/stdin << 'EOF' "$DATADIR/gits/morphs/test-chunk.morph" name: test-chunk kind: chunk build-system: manual # `install-commands` is a list of shell commands to run. Commands # may be on multiple lines, and indeed anything programmatic will # benefit from doing so. Arguably we could have just one command, # but it's split into multiple so that morph can inform us which # command failed without us having to include a lot of status # information in the command and look at the error message. install-commands: # 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 test-bin "$DESTDIR/$PREFIX/libexec/test-bin" # 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 test.h "$DESTDIR/$PREFIX/include/test.h" # `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 test.pc \ "$DESTDIR/$PREFIX/$pkgdir/pkgconfig/test.pc" done # Static libraries can be used to build static binaries, which don't # require their dependencies to be installed. They are typically in # the form of `.a` archive and `.la` libtool archives. - | for libdir in lib lib32 lib64; do for libname in libtest.a libtest.la; do install -D -m 644 /dev/null "$DESTDIR/$PREFIX/$libdir/$libname" done done # Packages may also install documentation, this comes in a variety # of formats, but info pages, man pages and html documentation are # the most common. - | for docfile in info/test.info.gz man/man3/test.3.gz doc/test/doc.html; do install -D -m 644 /dev/null "$DESTDIR/$PREFIX/share/$docfile" done # Locale covers translations, timezones, keyboard layouts etc. in # all manner of strange file formats and locations. # Locale provides various translations for specific messages. - | install -D -m 644 /dev/null \ "$DESTDIR/$PREFIX/share/locale/en_GB/LC_MESSAGES/test.mo" # Internationalisation (i18n) includes character maps and other data # such as currency. - | for localefile in i18n/locales/en_GB charmaps/UTF-8.gz; do install -D -m 644 /dev/null "$DESTDIR/$PREFIX/share/$localefile" done # Timezones are another kind of localisation. - | install -D -m 644 /dev/null "$DESTDIR/$PREFIX/share/zoneinfo/UTC" # We also need a catch rule for everything that doesn't fit into # the above categories, so to test that, we create some files that # don't belong in one. - | for cfgfile in test.conf README; do install -D -m 644 /dev/null "$DESTDIR/etc/test.d/$cfgfile" done 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" # Start a git daemon to serve our git repositories port_file="$DATADIR/git-daemon-port" pid_file="$DATADIR/git-daemon-pid" mkfifo "$port_file" # git-daemon needs --foo=bar style arguments so we do that for consistency start-stop-daemon --start --pidfile="$pid_file" --background \ --make-pidfile --verbose \ --startas="$SRCDIR/scripts/git-daemon-wrap" -- \ --port-file="$port_file" \ --export-all --verbose --base-path="$DATADIR/gits" \ --enable=receive-pack #allow push GIT_DAEMON_PORT="$(cat "$port_file")" # 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=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 cat << EOF > "$DATADIR/gits/morphs/stage1-chunk.morph" name: stage1-chunk kind: chunk build-system: dummy EOF run_in "$DATADIR/gits/morphs" git add . run_in "$DATADIR/gits/morphs" git commit -m "Add chunk for $MATCH_1" install -m644 -D /dev/stdin << EOF "$DATADIR/gits/morphs/strata/build-essential.morph" name: build-essential kind: stratum chunks: - name: stage1-chunk morph: stage1-chunk.morph repo: test:test-chunk ref: $(run_in "$DATADIR/gits/test-chunk" git rev-parse master) unpetrify-ref: master build-mode: bootstrap build-depends: [] EOF install -m644 -D /dev/stdin << EOF "$DATADIR/gits/morphs/strata/core.morph" name: core kind: stratum build-depends: - morph: strata/build-essential.morph chunks: - name: test-chunk morph: test-chunk.morph repo: test:test-chunk unpetrify-ref: master ref: $(run_in "$DATADIR/gits/test-chunk" git rev-parse master) build-mode: test build-depends: [] EOF 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." 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/systems/test-system.morph" is_file "$DATADIR/workspace/$MATCH_1/test/morphs/strata/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 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" 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: test-stratum morph: strata/test-stratum.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 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" 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 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 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" 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 the user (attempts to deploy|deploys) the (system|cluster) (\S+) in branch (\S+)( with options (.*))? cd "$DATADIR/workspace/$MATCH_4" set -- deploy "$MATCH_3" if [ "$MATCH_5" != '' ]; then # eval used so word splitting in the text is preserved eval set -- '"$@"' $MATCH_6 fi if [ $MATCH_1 == "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" 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" 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 build the system (\S+) of the (branch|tag) (\S+) cd "$DATADIR/workspace/$MATCH_3" run_morph build "$MATCH_1" IMPLEMENTS WHEN the user builds (\S+) of the (\S+) (branch|tag) cd "$DATADIR/workspace/$MATCH_2" run_morph build "$MATCH_1" 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"