summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDaniel Silverstone <dsilvers@digital-scurf.org>2013-08-25 14:45:19 +0100
committerDaniel Silverstone <dsilvers@digital-scurf.org>2013-08-25 14:45:19 +0100
commit59d1d18716527350ccefbfb8683b281639a98ca5 (patch)
tree45f6769d477f4478c345a24be4edc5bda8dee3e9
parent3cc579e60abe5a952ca2ebdc788b1d907c2cdede (diff)
parent57d45c8ccda13d061db17df16f50821c775cf277 (diff)
downloadgitano-59d1d18716527350ccefbfb8683b281639a98ca5.tar.gz
Merge branch 'dsilvers/testing'
We know it's incomplete, but meh.
-rw-r--r--Makefile27
-rw-r--r--testing/.gitignore1
-rw-r--r--testing/01-basics.yarn106
-rw-r--r--testing/02-commands-as.yarn72
-rw-r--r--testing/02-commands-config.yarn1
-rw-r--r--testing/02-commands-copy.yarn1
-rw-r--r--testing/02-commands-count-objects.yarn1
-rw-r--r--testing/02-commands-create.yarn1
-rw-r--r--testing/02-commands-destroy.yarn1
-rw-r--r--testing/02-commands-fsck.yarn65
-rw-r--r--testing/02-commands-gc.yarn69
-rw-r--r--testing/02-commands-graveyard.yarn1
-rw-r--r--testing/02-commands-group.yarn1
-rw-r--r--testing/02-commands-help.yarn1
-rw-r--r--testing/02-commands-ls.yarn21
-rw-r--r--testing/02-commands-readme.yarn1
-rw-r--r--testing/02-commands-rename.yarn1
-rw-r--r--testing/02-commands-set-description.yarn1
-rw-r--r--testing/02-commands-set-head.yarn1
-rw-r--r--testing/02-commands-set-owner.yarn1
-rw-r--r--testing/02-commands-sshkey.yarn1
-rw-r--r--testing/02-commands-user.yarn1
-rw-r--r--testing/02-commands-whoami.yarn1
-rw-r--r--testing/gitano-test-tool.in181
-rw-r--r--testing/library.yarn104
25 files changed, 662 insertions, 0 deletions
diff --git a/Makefile b/Makefile
index 8c49fc4..182b9ea 100644
--- a/Makefile
+++ b/Makefile
@@ -25,6 +25,17 @@ LIB_BINS := gitano-auth gitano-post-receive-hook gitano-update-hook \
BINS := gitano-setup
+TEST_BIN_NAMES := gitano-test-tool
+
+TESTS := 01-basics 02-commands-as 02-commands-config 02-commands-copy \
+ 02-commands-count-objects 02-commands-create 02-commands-destroy \
+ 02-commands-fsck 02-commands-gc 02-commands-graveyard \
+ 02-commands-group 02-commands-help 02-commands-ls 02-commands-readme \
+ 02-commands-rename 02-commands-set-description 02-commands-set-head \
+ 02-commands-set-owner 02-commands-sshkey 02-commands-user \
+ 02-commands-whoami
+
+
MODS := gitano \
\
gitano.util \
@@ -54,6 +65,13 @@ SRC_MOD_FILES := $(patsubst %,lib/%,$(MOD_FILES))
LOCAL_BINS := $(patsubst %,bin/%,$(BINS) $(LIB_BINS))
LIB_BIN_SRCS := $(patsubst %,bin/%.in,$(LIB_BINS))
+TEST_BINS := $(patsubst %,testing/%,$(TEST_BIN_NAMES))
+TEST_BIN_SRCS := $(patsubst %,%.in,$(TEST_BINS))
+
+YARN := yarn
+
+TESTS := $(patsubst %,testing/%.yarn,$(TESTS))
+
GEN_BIN := utils/install-lua-bin
RUN_GEN_BIN := $(LUA) $(GEN_BIN) $(LUA)
define GEN_LOCAL_BIN
@@ -88,6 +106,8 @@ local: $(LOCAL_BINS)
clean:
@echo "CLEAN: local binaries"
@$(RM) $(LOCAL_BINS)
+ @echo "CLEAN: test binaries"
+ @$(RM) $(TEST_BINS)
distclean: clean
@find . -name "*~" -delete
@@ -118,3 +138,10 @@ install-skel:
for SKELFILE in $(SKEL_FILES); do \
install -m 644 skel/$$SKELFILE $(SKEL_INST_PATH)/$$SKELFILE; \
done
+
+test: local $(TEST_BINS)
+ @$(YARN) --env GTT="$$(pwd)/testing/gitano-test-tool" \
+ testing/library.yarn $(TESTS)
+
+testing/%: testing/%.in $(GEN_BIN)
+ $(call GEN_LOCAL_BIN,$<,$@)
diff --git a/testing/.gitignore b/testing/.gitignore
new file mode 100644
index 0000000..bf632a4
--- /dev/null
+++ b/testing/.gitignore
@@ -0,0 +1 @@
+gitano-test-tool
diff --git a/testing/01-basics.yarn b/testing/01-basics.yarn
new file mode 100644
index 0000000..18f2e4f
--- /dev/null
+++ b/testing/01-basics.yarn
@@ -0,0 +1,106 @@
+<!-- -*- markdown -*- -->
+Basic tests for Gitano
+======================
+
+In these basic tests for Gitano we start life by creating a standard
+installation and verifying some of the very basics of Gitano's core
+functionality.
+
+Basic behaviour
+---------------
+
+In this scenario we verify that we can create a standard instance and then
+clone the `gitano-admin` repository. Once we've done that we also verify that
+we can create a user, give it an ssh key, that the user's creation is reflected
+in the `gitano-admin` repository and that we can remove the user and the
+removal is also reflected.
+
+ SCENARIO Verification of basic behaviour
+
+Step 1 is to create a standard instance and clone `gitano-admin`
+
+ GIVEN a standard instance
+ WHEN testinstance, using adminkey, clones gitano-admin as gitano-admin
+ THEN testinstance has a clone of gitano-admin
+
+Next we create the user (alice) and verify that `gitano-admin` shows her.
+
+ GIVEN a unix user called alice
+ AND alice has keys called main
+ WHEN testinstance, using adminkey, adds user alice, using alice main
+ AND git pull happens in testinstance gitano-admin
+ THEN testinstance gitano-admin has a file called users/alice/user.conf
+ AND testinstance gitano-admin has a file called users/alice/default.key
+
+Finally we remove that user and verify that `gitano-admin` reflects that too.
+
+ WHEN testinstance, using adminkey, deletes user alice
+ AND git pull happens in testinstance gitano-admin
+ THEN testinstance gitano-admin has no file called users/alice/user.conf
+ AND testinstance gitano-admin has no file called users/alice/default.key
+
+Users can see what groups they are in
+-------------------------------------
+
+In this scenario we take a standard instance and ensure that the `testinstance`
+user can access their user information (their `whoami` output) and that
+information lists the `gitano-admin` group which they should be a member of.
+
+ SCENARIO whoami shows the gitano-admin group
+
+ GIVEN a standard instance
+ WHEN testinstance adminkey runs whoami
+ THEN stdout contains gitano-admin
+
+Then, just to be sure, we create a new user and ensure that it does not have
+membership of `gitano-admin`
+
+ GIVEN a unix user called alice
+ AND alice has keys called main
+ WHEN testinstance, using adminkey, adds user alice, using alice main
+ AND alice main runs whoami
+ THEN stdout does not contain gitano-admin
+
+Non-admin users cannot see the `gitano-admin` repository
+--------------------------------------------------------
+
+In this scenario we take a standard instance, add a user to it, and verify that
+when the new user runs 'ls' it doesn't get to see `gitano-admin` but that the
+`testinstance` user gets to see it and has `RW` privs.
+
+ SCENARIO ls will not show repositories you have no access to
+
+ GIVEN a standard instance
+ AND a unix user called alice
+ AND alice has keys called main
+ WHEN testinstance, using adminkey, adds user alice, using alice main
+ AND alice main runs ls
+ THEN stdout does not contain gitano-admin
+ WHEN testinstance adminkey runs ls
+ THEN stdout contains RW gitano-admin
+
+Basic repository creation
+-------------------------
+
+In a default configuration, the only user who will be able to create
+repositories. However creation can hand off ownership which means that we can
+test that a new user who has a repository created for them can see it in ls.
+
+ SCENARIO delegated repository creation works
+
+ GIVEN a standard instance
+ AND a unix user called alice
+ AND alice has keys called main
+ WHEN testinstance, using adminkey, adds user alice, using alice main
+ AND testinstance adminkey runs create somerepo alice
+ AND alice main runs ls
+ THEN stdout contains RW somerepo
+
+And just to check, if the `testinstance` user created a non-delegated repo then
+the `alice` user cannot see it.
+
+ WHEN testinstance adminkey runs create anotherrepo
+ AND testinstance adminkey runs ls
+ THEN stdout contains RW anotherrepo
+ WHEN alice main runs ls
+ THEN stdout does not contain anotherrepo
diff --git a/testing/02-commands-as.yarn b/testing/02-commands-as.yarn
new file mode 100644
index 0000000..ce8afbf
--- /dev/null
+++ b/testing/02-commands-as.yarn
@@ -0,0 +1,72 @@
+<!-- -*- markdown -*- -->
+as --A- Become someone else
+===========================
+
+The `as` command can be used to run commands as different users. It should not
+leak the existence/absence of a user, nor should it leak permissions from the
+calling user into the effective user.
+
+Verification of `as` in the simple case
+---------------------------------------
+
+In the simple case, `as` is being called by someone who has permission to do
+so, on behalf of a user which exists and can be used.
+
+ SCENARIO Default case for as
+
+ GIVEN a standard instance
+ AND testinstance has keys called other
+ WHEN testinstance, using adminkey, adds user other, using testinstance other
+ AND testinstance adminkey runs as other whoami
+ THEN stdout contains other
+ AND stdout does not contain gitano-admin
+
+The other trivial case is that a user without permission tries to run `as`.
+
+ WHEN testinstance other, expecting failure, runs as other whoami
+ THEN stdout is empty
+ AND stderr contains Ruleset denied action
+ AND stderr contains exit:1
+
+The final trivial case is that a user which can run `as` cannot use it to run
+`as`.
+
+ WHEN testinstance adminkey, expecting failure, runs as other as other whoami
+ THEN stdout is empty
+ AND stderr contains Cannot use 'as' to run 'as'
+ AND stderr contains Validation of command line failed
+ AND stderr contains exit:1
+
+Security-related cases for `as` invocation
+------------------------------------------
+
+There are a number of security implications for the `as` command. In the
+simplest of cases it is only necessary to grant gitano-admin members the right
+to run commands `as` other users. In this way, only those who could otherwise
+alter the users in the first place can act on their behalf.
+
+There is, however, a potential information leak -- namely if someone who does
+not have the right to run commands 'as' another user runs an `as` with a user
+which does not exist. It is critical that this simply be reported as a lack of
+permission to run any command, and not leak that the target user does not exist
+in any way.
+
+ SCENARIO Ensuring 'as' does not leak user presence
+
+ GIVEN a standard instance
+ AND testinstance has keys called other
+ WHEN testinstance, using adminkey, adds user other, using testinstance other
+ AND testinstance adminkey runs as other whoami
+ THEN stderr is empty
+ WHEN testinstance other, expecting failure, runs as badger whoami
+ THEN stdout is empty
+ AND stderr does not contain badger
+
+Finally we ensure that when a user who may run `as` commands does so, but
+manages to typo a username, they get a useful error message.
+
+ WHEN testinstance adminkey, expecting failure, runs as badger whoami
+ THEN stderr contains badger
+ AND stderr contains does not exist
+ AND stderr contains exit:1
+ AND stdout is empty
diff --git a/testing/02-commands-config.yarn b/testing/02-commands-config.yarn
new file mode 100644
index 0000000..f282cb0
--- /dev/null
+++ b/testing/02-commands-config.yarn
@@ -0,0 +1 @@
+<!-- -*- markdown -*- -->
diff --git a/testing/02-commands-copy.yarn b/testing/02-commands-copy.yarn
new file mode 100644
index 0000000..f282cb0
--- /dev/null
+++ b/testing/02-commands-copy.yarn
@@ -0,0 +1 @@
+<!-- -*- markdown -*- -->
diff --git a/testing/02-commands-count-objects.yarn b/testing/02-commands-count-objects.yarn
new file mode 100644
index 0000000..f282cb0
--- /dev/null
+++ b/testing/02-commands-count-objects.yarn
@@ -0,0 +1 @@
+<!-- -*- markdown -*- -->
diff --git a/testing/02-commands-create.yarn b/testing/02-commands-create.yarn
new file mode 100644
index 0000000..f282cb0
--- /dev/null
+++ b/testing/02-commands-create.yarn
@@ -0,0 +1 @@
+<!-- -*- markdown -*- -->
diff --git a/testing/02-commands-destroy.yarn b/testing/02-commands-destroy.yarn
new file mode 100644
index 0000000..f282cb0
--- /dev/null
+++ b/testing/02-commands-destroy.yarn
@@ -0,0 +1 @@
+<!-- -*- markdown -*- -->
diff --git a/testing/02-commands-fsck.yarn b/testing/02-commands-fsck.yarn
new file mode 100644
index 0000000..115bbc6
--- /dev/null
+++ b/testing/02-commands-fsck.yarn
@@ -0,0 +1,65 @@
+<!-- -*- markdown -*- -->
+fsck ---- Perform a fsck operation on a repository (Takes a repo)
+=================================================================
+
+The `fsck` command is a basic pass-through to the underlying `git fsck` being
+run on the remote repository. Apart from ensuring that the caller has `write`
+access, to a repository which exists, no other checks are done and any spare
+arguments are passed through to `git fsck`.
+
+Simple `fsck` usage
+-------------------
+
+In the simple case a `gitano-admin` runs `fsck` against a repository which
+definitely exists and as they are `gitano-admin` they have write access.
+
+ SCENARIO Simple `fsck` cases
+
+ GIVEN a standard instance
+ WHEN testinstance adminkey runs fsck gitano-admin
+ THEN stdout is empty
+ AND stderr is empty
+
+No matter how powerful you are, you cannot `fsck` a repository which does not
+exist...
+
+ WHEN testinstance adminkey, expecting failure, runs fsck somethingelse
+ THEN stdout is empty
+ AND stderr contains repository does not exist
+
+Attempting to `fsck` when you have no write access
+--------------------------------------------------
+
+Since any non-`gitano-admin` member cannot see `gitano-admin` we can use that
+as a test case for ensuring that you must have write access in order to `fsck`
+something.
+
+ SCENARIO lowly accolyte fails to fsck
+
+ GIVEN a standard instance
+ AND testinstance has keys called other
+ WHEN testinstance, using adminkey, adds user other, using testinstance other
+ AND testinstance other, expecting failure, runs fsck gitano-admin
+ THEN stdout is empty
+ AND stderr contains Ruleset denied action
+
+Passing commands through to `fsck`
+----------------------------------
+
+It is possible to pass arguments through to the `git fsck` subprocess. By
+passing through a bad option, we get to see this in action
+
+ SCENARIO passing arguments to `fsck`
+
+ GIVEN a standard instance
+ WHEN testinstance adminkey, expecting failure, runs fsck gitano-admin --bad-option
+ THEN stdout is empty
+
+We check for `git fsck`'s usage message:
+
+ AND stderr contains error: unknown option
+ AND stderr contains usage: git fsck
+
+And also we see that Gitano has caught the error
+
+ AND stderr contains Unable to continue
diff --git a/testing/02-commands-gc.yarn b/testing/02-commands-gc.yarn
new file mode 100644
index 0000000..d1cff46
--- /dev/null
+++ b/testing/02-commands-gc.yarn
@@ -0,0 +1,69 @@
+<!-- -*- markdown -*- -->
+gc ---- Invoke git gc on your repository (Takes a repo)
+=======================================================
+
+The `gc` command is a basic pass-through to the underlying `git gc` being run
+on the remote repository. Apart from ensuring that the caller has `write`
+access, to a repository which exists, no other checks are done and any spare
+arguments are passed through to `git gc`.
+
+Using `gc` in the simple case
+-----------------------------
+
+So the simple case is that a `gitano-admin` runs `gc` on a repository which
+definitely exists which means they always have the rights to do so.
+
+ SCENARIO Simple case `gc` usage
+
+ GIVEN a standard instance
+ WHEN testinstance adminkey runs gc gitano-admin
+ THEN stderr is empty
+ AND stdout is empty
+
+We can then ensure that if the repository does not exist, we get a useful error
+message back:
+
+ SCENARIO Simple failure case `gc` usage
+
+ GIVEN a standard instance
+ WHEN testinstance adminkey, expecting failure, runs gc something
+ THEN stdout is empty
+ AND stderr contains repository does not exist
+
+Write access checks
+-------------------
+
+A more complex case involves creating a repository to which a user has no write
+permissions and trying to get that user to run `gc` on it.
+
+ SCENARIO Write access checks for `gc` usage
+
+ GIVEN a standard instance
+ AND testinstance has keys called other
+ WHEN testinstance, using adminkey, adds user other, using testinstance other
+ AND testinstance adminkey runs create testrepo
+ AND testinstance other, expecting failure, runs gc testrepo
+ THEN stdout is empty
+ AND stderr contains Ruleset denied action
+
+Passing arguments to `git gc`
+-----------------------------
+
+Any spare arguments given to `gc` are passed through to `git gc` untouched. We
+can verify that arguments are passed through by passing a bad argument through
+and seeing if we get an error message from the underlying `git gc` instance:
+
+ SCENARIO Passing arguments through to `git gc`
+
+ GIVEN a standard instance
+ WHEN testinstance adminkey, expecting failure, runs gc gitano-admin --not-valid
+ THEN stdout is empty
+
+These are the `git gc` errors
+
+ AND stderr contains error: unknown option
+ AND stderr contains usage: git gc
+
+And this demonstrates that Gitano detected the error properly
+
+ AND stderr contains Unable to continue
diff --git a/testing/02-commands-graveyard.yarn b/testing/02-commands-graveyard.yarn
new file mode 100644
index 0000000..f282cb0
--- /dev/null
+++ b/testing/02-commands-graveyard.yarn
@@ -0,0 +1 @@
+<!-- -*- markdown -*- -->
diff --git a/testing/02-commands-group.yarn b/testing/02-commands-group.yarn
new file mode 100644
index 0000000..f282cb0
--- /dev/null
+++ b/testing/02-commands-group.yarn
@@ -0,0 +1 @@
+<!-- -*- markdown -*- -->
diff --git a/testing/02-commands-help.yarn b/testing/02-commands-help.yarn
new file mode 100644
index 0000000..f282cb0
--- /dev/null
+++ b/testing/02-commands-help.yarn
@@ -0,0 +1 @@
+<!-- -*- markdown -*- -->
diff --git a/testing/02-commands-ls.yarn b/testing/02-commands-ls.yarn
new file mode 100644
index 0000000..8ead9da
--- /dev/null
+++ b/testing/02-commands-ls.yarn
@@ -0,0 +1,21 @@
+<!-- -*- markdown -*- -->
+
+`ls [--verbose|-v] [<pattern>...]`
+==================================
+
+The `ls` command is one of the few which touch every repository in a Gitano
+instance. As such, it can take a while to run. Theoretically it leaks the
+number of Git repositories on the server by virtue of analysis of timing.
+
+Basic operation
+===============
+
+Firstly, we verify the basic operation of ls, that as a gitano-admin we have
+read access (at least) to everything and as such we can list all the
+repositories.
+
+ SCENARIO Basic operation of ls
+ GIVEN a standard instance
+ WHEN testinstance adminkey runs ls
+ THEN stdout contains RW gitano-admin
+ AND stderr is empty
diff --git a/testing/02-commands-readme.yarn b/testing/02-commands-readme.yarn
new file mode 100644
index 0000000..f282cb0
--- /dev/null
+++ b/testing/02-commands-readme.yarn
@@ -0,0 +1 @@
+<!-- -*- markdown -*- -->
diff --git a/testing/02-commands-rename.yarn b/testing/02-commands-rename.yarn
new file mode 100644
index 0000000..f282cb0
--- /dev/null
+++ b/testing/02-commands-rename.yarn
@@ -0,0 +1 @@
+<!-- -*- markdown -*- -->
diff --git a/testing/02-commands-set-description.yarn b/testing/02-commands-set-description.yarn
new file mode 100644
index 0000000..f282cb0
--- /dev/null
+++ b/testing/02-commands-set-description.yarn
@@ -0,0 +1 @@
+<!-- -*- markdown -*- -->
diff --git a/testing/02-commands-set-head.yarn b/testing/02-commands-set-head.yarn
new file mode 100644
index 0000000..f282cb0
--- /dev/null
+++ b/testing/02-commands-set-head.yarn
@@ -0,0 +1 @@
+<!-- -*- markdown -*- -->
diff --git a/testing/02-commands-set-owner.yarn b/testing/02-commands-set-owner.yarn
new file mode 100644
index 0000000..f282cb0
--- /dev/null
+++ b/testing/02-commands-set-owner.yarn
@@ -0,0 +1 @@
+<!-- -*- markdown -*- -->
diff --git a/testing/02-commands-sshkey.yarn b/testing/02-commands-sshkey.yarn
new file mode 100644
index 0000000..f282cb0
--- /dev/null
+++ b/testing/02-commands-sshkey.yarn
@@ -0,0 +1 @@
+<!-- -*- markdown -*- -->
diff --git a/testing/02-commands-user.yarn b/testing/02-commands-user.yarn
new file mode 100644
index 0000000..f282cb0
--- /dev/null
+++ b/testing/02-commands-user.yarn
@@ -0,0 +1 @@
+<!-- -*- markdown -*- -->
diff --git a/testing/02-commands-whoami.yarn b/testing/02-commands-whoami.yarn
new file mode 100644
index 0000000..f282cb0
--- /dev/null
+++ b/testing/02-commands-whoami.yarn
@@ -0,0 +1 @@
+<!-- -*- markdown -*- -->
diff --git a/testing/gitano-test-tool.in b/testing/gitano-test-tool.in
new file mode 100644
index 0000000..c931b6d
--- /dev/null
+++ b/testing/gitano-test-tool.in
@@ -0,0 +1,181 @@
+-- @@SHEBANG
+-- -*- lua -*-
+-- gitano-test-tool
+--
+-- Git (with) Augmented network operations -- testing tool
+--
+-- Copyright 2012 Daniel Silverstone <dsilvers@digital-scurf.org>
+--
+--
+
+-- @@GITANO_LUA_PATH
+
+local gitano = require "gitano"
+local gall = require "gall"
+local luxio = require "luxio"
+local sio = require "luxio.simple"
+local sp = require "luxio.subprocess"
+
+-- @@GITANO_BIN_PATH
+-- @@GITANO_SHARE_PATH
+
+local argv = {...}
+local basedir = (luxio.getenv "DATADIR") .. "/"
+
+local function user_home(username)
+ return basedir .. "user-home-" .. username
+end
+
+local function ssh_base(username)
+ return user_home(username) .. "/.ssh"
+end
+
+local function ssh_key_file(username, keyname)
+ return ssh_base(username) .. "/" .. keyname
+end
+
+local function unix_assert(ret, errno)
+ if ret ~= 0 then
+ error(luxio.strerror(errno))
+ end
+end
+
+local function run_program(t)
+ local proc = sp.spawn_simple(t)
+ local how, why = proc:wait()
+ if how == -1 then
+ unix_assert(how, why)
+ end
+ if not (how == "exit" and why == 0) then
+ io.stderr:write(how .. ":" .. tostring(why).."\n")
+ os.exit(1)
+ end
+end
+
+local function esc_quote_all(t)
+ local tt = {}
+ for i = 1, #t do
+ tt[i] = ("%q"):format(t[i])
+ end
+ return table.concat(tt, " ")
+end
+
+local function load_auth(fname)
+ local fh = io.open(fname, "r")
+ local line = fh:read("*l")
+ local ret = {}
+ while line do
+ line = line:gsub("^ *", "")
+ line = line:gsub(" *$", "")
+ line = line:gsub("^#.*", "")
+ if line ~= "" then
+ local repopath, user, keyset, key =
+ line:match('^[^\\]+\\"([^"]+)\\" \\"([^"]+)\\" \\"([^"]+)\\""[^ ]+ (.+)$')
+ assert(repopath, line)
+ ret[#ret+1] = {
+ repopath = repopath,
+ user = user,
+ keyset = keyset,
+ key = key
+ }
+ ret[key] = ret[#ret]
+ end
+ line = fh:read("*l")
+ end
+ fh:close()
+ return ret
+end
+
+local function generate_exturl(user, key, repo)
+ local authkeys = load_auth(ssh_key_file("testinstance", "authorized_keys"))
+ local pubkey = (sio.open(ssh_key_file(user, key) .. ".pub", "r")):read("*l")
+ local authline = assert(authkeys[pubkey])
+ local extfmt = "ext::env HOME=%s SSH_ORIGINAL_COMMAND=%s %s %s %s %s"
+ local function esc(s)
+ return ((s:gsub("%%", "%%%%")):gsub(" ", "%% "))
+ end
+ return (extfmt):format(esc(user_home("testinstance")),
+ "%S% " .. esc(repo),
+ esc(gitano.config.lib_bin_path() .. "/gitano-auth"),
+ esc(authline.repopath),
+ esc(authline.user), esc(authline.keyset))
+end
+
+function cmd_createunixuser(username)
+ assert(sio.mkdir(user_home(username), "0755"))
+ assert(sio.mkdir(ssh_base(username), "0755"))
+end
+
+function cmd_createsshkey(username, keyname, optionaltype)
+ optionaltype = optionaltype or "rsa"
+ run_program {
+ "ssh-keygen", "-q",
+ "-t", optionaltype,
+ "-C", username .. "-" .. optionaltype .. "@" .. keyname,
+ "-f", ssh_key_file(username, keyname),
+ "-N", "" }
+end
+
+function cmd_setupstandard(owning_user, master_key)
+ local clodname = basedir .. "setup.clod"
+ local fh = io.open(clodname, "w")
+ fh:write('setup.batch "true"\n')
+ fh:write(('paths.pubkey %q\n'):format(ssh_key_file(owning_user, master_key) .. ".pub"))
+ fh:write('site.name "Gitano Test Instance"\n')
+ fh:write('log.prefix "gitano-test"\n')
+ fh:write(('admin.keyname %q\n'):format(master_key))
+ fh:close()
+ run_program {
+ "gitano-setup", clodname,
+ exe = gitano.config.lib_bin_path() .. "/gitano-setup",
+ env = { HOME = user_home(owning_user) }
+ }
+end
+
+function cmd_cloneviassh(user, key, repo, localname)
+ local exturl = generate_exturl(user, key, repo)
+ run_program {
+ "git", "clone", exturl, user_home(user) .. "/" .. localname,
+ }
+end
+
+function cmd_cloneexists(user, localname)
+ run_program {
+ "git", "fsck", user_home(user) .. "/" .. localname
+ }
+end
+
+function cmd_pubkeyfilename(user, key)
+ print(ssh_key_file(user, key) .. ".pub")
+end
+
+function cmd_runcommand(user, key, ...)
+ local authkeys = load_auth(ssh_key_file("testinstance", "authorized_keys"))
+ local pubkey = (sio.open(ssh_key_file(user, key) .. ".pub", "r")):read("*l")
+ local authline = assert(authkeys[pubkey])
+ local cmdline = {
+ gitano.config.lib_bin_path() .. "/gitano-auth",
+ authline.repopath, authline.user, authline.keyset,
+ env = {HOME = user_home("testinstance")}
+ }
+ cmdline.env.SSH_ORIGINAL_COMMAND = esc_quote_all({...})
+ run_program(cmdline)
+end
+
+function cmd_clonelocation(user, localname)
+ print(user_home(user) .. "/" .. localname)
+end
+
+function cmd_findtoken()
+ local input = sio.stdin:read("*a")
+ local token = input:match("("..("[0-9a-f]"):rep(40)..")")
+ assert(token, "Cannot find a token")
+ print(token)
+end
+
+local cmd = table.remove(argv, 1)
+if _G['cmd_' .. cmd] then
+ _G['cmd_' .. cmd](unpack(argv))
+else
+ error("Unknown command: " .. cmd)
+end
diff --git a/testing/library.yarn b/testing/library.yarn
new file mode 100644
index 0000000..c49021e
--- /dev/null
+++ b/testing/library.yarn
@@ -0,0 +1,104 @@
+<!-- -*- markdown -*- -->
+Test library for Gitano
+=======================
+
+When running tests under yarn, for each scenario, we are provided with a
+temporary working directory called `$DATADIR` which is a fresh directory for
+each scenario being run. Within that base, we can set up any number of fake
+SSH keys, a fake Gitano instance, fake users, and use them to make clones, do
+pushes etc. Nearly all of the implementations rely on a tool in the testing
+directory called `gitano-test-tool` the path to which is available as `$GTT`.
+
+For ease of testing, the fake user who gets to "own" the Gitano instance will
+be called `testinstance` and the keyset which they get to use in order to
+access the repository will be called `adminkey`. This is important when it
+comes to cloning, pushing, etc.
+
+Managing the fake unix users
+----------------------------
+
+ IMPLEMENTS GIVEN a unix user called ([a-z][a-z0-9]*)
+ $GTT createunixuser $MATCH_1
+
+ IMPLEMENTS GIVEN ([a-z][a-z0-9]*) has keys called ([a-z][a-z0-9]*)
+ $GTT createsshkey $MATCH_1 $MATCH_2
+
+General instance management
+---------------------------
+
+ IMPLEMENTS GIVEN a standard instance
+ $GTT createunixuser testinstance
+ $GTT createsshkey testinstance adminkey
+ $GTT setupstandard testinstance adminkey
+
+Repository access
+-----------------
+
+ IMPLEMENTS WHEN ([a-z][a-z0-9]*),? using ([a-z][a-z0-9]*),? clones ([^ ]+) as ([^ ]+)
+ $GTT cloneviassh $MATCH_1 $MATCH_2 "$MATCH_3" "$MATCH_4"
+
+Clone manipulation
+------------------
+
+ IMPLEMENTS THEN ([a-z][a-z0-9]*) has a clone of ([^ ]+)
+ $GTT cloneexists $MATCH_1 "$MATCH_2"
+
+ IMPLEMENTS WHEN git pull happens in ([a-z][a-z0-9]*) ([^ ]+)
+ cd "$($GTT clonelocation $MATCH_1 "$MATCH_2")"
+ git pull
+
+ IMPLEMENTS THEN ([a-z][a-z0-9]*) ([^ ]+) has a file called (.+)
+ cd "$($GTT clonelocation $MATCH_1 "$MATCH_2")"
+ test -r "$MATCH_3"
+
+ IMPLEMENTS THEN ([a-z][a-z0-9]*) ([^ ]+) has no file called (.+)
+ set -x
+ cd "$($GTT clonelocation $MATCH_1 "$MATCH_2")"
+ if test -r "$MATCH_3"; then false; else true; fi
+
+Admin repo manipulation
+-----------------------
+
+ IMPLEMENTS WHEN ([a-z][a-z0-9]*),? using ([a-z][a-z0-9]*),? adds user ([a-z][a-z0-9]*),? using ([a-z][a-z0-9]*) ([a-z][a-z0-9]*)
+ $GTT runcommand $MATCH_1 $MATCH_2 \
+ user add $MATCH_3 $MATCH_3@testinstance "$MATCH_3's real name"
+ $GTT runcommand $MATCH_1 $MATCH_2 \
+ as $MATCH_3 sshkey add default < \
+ $($GTT pubkeyfilename $MATCH_4 $MATCH_5)
+
+ IMPLEMENTS WHEN ([a-z][a-z0-9]*),? using ([a-z][a-z0-9]*),? deletes user ([a-z][a-z0-9]*)
+ TOKEN=$($GTT runcommand $MATCH_1 $MATCH_2 user del $MATCH_3 2>&1 | $GTT findtoken)
+ $GTT runcommand $MATCH_1 $MATCH_2 user del $MATCH_3 $TOKEN
+
+Generic utility methods
+-----------------------
+
+ IMPLEMENTS WHEN ([a-z][a-z0-9]*) ([a-z][a-z0-9]*) runs (.+)
+ $GTT runcommand $MATCH_1 $MATCH_2 $MATCH_3 > $DATADIR/stdout 2> $DATADIR/stderr
+
+ IMPLEMENTS WHEN ([a-z][a-z0-9]*) ([a-z][a-z0-9]*),? expecting failure,? runs (.+)
+ if $GTT runcommand $MATCH_1 $MATCH_2 $MATCH_3 > $DATADIR/stdout 2> $DATADIR/stderr; then
+ false
+ fi
+
+ IMPLEMENTS THEN ([^ ]+) contains (.+)
+ grep -q "$MATCH_2" $DATADIR/"$MATCH_1"
+
+ IMPLEMENTS THEN ([^ ]+) does not contain (.+)
+ if grep -q "$MATCH_2" $DATADIR/"$MATCH_1"; then false; else true; fi
+
+ IMPLEMENTS THEN ([^ ]+) is empty
+ if grep -q . $DATADIR/"$MATCH_1"; then false; fi
+
+ IMPLEMENTS THEN failure ensues
+ cd $DATADIR
+ echo "FIND:"
+ find .
+ echo "KEYS:"
+ cat user-home-testinstance/.ssh/authorized_keys
+ echo "OUT":
+ cat stdout
+ echo "ERR":
+ cat stderr
+ /bin/false
+