summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--morphlib/bins.py92
-rw-r--r--morphlib/bins_tests.py4
-rw-r--r--morphlib/builder2.py47
-rw-r--r--morphlib/stagingarea.py85
-rwxr-xr-xscripts/list-tree22
-rwxr-xr-xtests.as-root/make-patch.script19
-rwxr-xr-xtests.as-root/setup18
-rwxr-xr-xtests.as-root/system-overlap.script123
-rw-r--r--tests.as-root/system-overlap.stdout7
-rwxr-xr-xtests/stratum-overlap-keeps-directory-links.script32
l---------tests/stratum-overlap-keeps-directory-links.setup1
-rwxr-xr-xtests/stratum-overlap-stomps-file-links.script32
l---------tests/stratum-overlap-stomps-file-links.setup1
-rwxr-xr-xtests/stratum-overlap-warns.script28
-rwxr-xr-xtests/stratum-overlap-warns.setup113
-rw-r--r--tests/stratum-overlap-warns.stdout5
16 files changed, 510 insertions, 119 deletions
diff --git a/morphlib/bins.py b/morphlib/bins.py
index 8fd01182..f42951b7 100644
--- a/morphlib/bins.py
+++ b/morphlib/bins.py
@@ -24,6 +24,8 @@ Binaries are chunks, strata, and system images.
import logging
import os
import re
+import errno
+import stat
import tarfile
@@ -109,17 +111,87 @@ def unpack_binary_from_file(f, dirname): # pragma: no cover
'''
tf = tarfile.open(fileobj=f)
- tf.extractall(path=dirname)
- tf.close()
-
-
-def unpack_binary(filename, dirname, ex):
- '''Unpack a binary into a directory.
+
+ # This is evil, but necessary. For some reason Python's system
+ # call wrappers (os.mknod and such) do not (always?) set the
+ # filename attribute of the OSError exception they raise. We
+ # fix that by monkey patching the tf instance with wrappers
+ # for the relevant methods to add things. The wrapper further
+ # ignores EEXIST errors, since we do not (currently!) care about
+ # overwriting files.
- The directory must exist already.
+ def follow_symlink(path): # pragma: no cover
+ try:
+ return os.stat(path)
+ except OSError:
+ return None
- '''
+ def prepare_extract(tarinfo, targetpath): # pragma: no cover
+ '''Prepare to extract a tar file member onto targetpath?
+
+ If the target already exist, and we can live with it or
+ remove it, we do so. Otherwise, raise an error.
+
+ It's OK to extract if:
+
+ * the target does not exist
+ * the member is a directory a directory and the
+ target is a directory or a symlink to a directory
+ (just extract, no need to remove)
+ * the member is not a directory, and the target is not a directory
+ or a symlink to a directory (remove target, then extract)
+
+ '''
+
+ try:
+ existing = os.lstat(targetpath)
+ except OSError:
+ return True # target does not exist
+
+ if tarinfo.isdir():
+ if stat.S_ISDIR(existing.st_mode):
+ return True
+ elif stat.S_ISLNK(existing.st_mode):
+ st = follow_symlink(targetpath)
+ return st and stat.S_ISDIR(st.st_mode)
+ else:
+ if stat.S_ISDIR(existing.st_mode):
+ return False
+ elif stat.S_ISLNK(existing.st_mode):
+ st = follow_symlink(targetpath)
+ if st and not stat.S_ISDIR(st.st_mode):
+ os.remove(targetpath)
+ return True
+ else:
+ os.remove(targetpath)
+ return True
+ return False
+
+ def monkey_patcher(real):
+ def make_something(tarinfo, targetpath): # pragma: no cover
+ prepare_extract(tarinfo, targetpath)
+ try:
+ return real(tarinfo, targetpath)
+ except OSError, e:
+ if e.errno != errno.EEXIST:
+ if e.filename is None:
+ e.filename = targetpath
+ raise e
+ else:
+ raise
+ return make_something
+
+ tf.makedir = monkey_patcher(tf.makedir)
+ tf.makefile = monkey_patcher(tf.makefile)
+ tf.makeunknown = monkey_patcher(tf.makeunknown)
+ tf.makefifo = monkey_patcher(tf.makefifo)
+ tf.makedev = monkey_patcher(tf.makedev)
+ tf.makelink = monkey_patcher(tf.makelink)
- logging.debug('Unpacking %s into %s' % (filename, dirname))
- ex.runv(['tar', '-C', dirname, '-xvhf', filename])
+ tf.extractall(path=dirname)
+ tf.close
+def unpack_binary(filename, dirname):
+ f = open(filename, "rb")
+ unpack_binary_from_file(f, dirname)
+ f.close()
diff --git a/morphlib/bins_tests.py b/morphlib/bins_tests.py
index 82ceb246..dd2fb886 100644
--- a/morphlib/bins_tests.py
+++ b/morphlib/bins_tests.py
@@ -113,7 +113,7 @@ class ChunkTests(BinsTest):
def unpack_chunk(self):
os.mkdir(self.unpacked)
- morphlib.bins.unpack_binary(self.chunk_file, self.unpacked, self.ex)
+ morphlib.bins.unpack_binary(self.chunk_file, self.unpacked)
def test_empties_everything(self):
self.create_chunk(['.'])
@@ -158,7 +158,7 @@ class StratumTests(BinsTest):
morphlib.bins.create_stratum(self.instdir, self.stratum_f, self.ex)
self.stratum_f.flush()
os.mkdir(self.unpacked)
- morphlib.bins.unpack_binary(self.stratum_file, self.unpacked, self.ex)
+ morphlib.bins.unpack_binary(self.stratum_file, self.unpacked)
self.assertEqual(self.recursive_lstat(self.instdir),
self.recursive_lstat(self.unpacked))
diff --git a/morphlib/builder2.py b/morphlib/builder2.py
index 0e5c469f..62e484f4 100644
--- a/morphlib/builder2.py
+++ b/morphlib/builder2.py
@@ -19,6 +19,8 @@ import logging
import os
import shutil
import time
+from collections import defaultdict
+import tarfile
import morphlib
@@ -60,6 +62,31 @@ def ldconfig(ex, rootdir): # pragma: no cover
logging.debug('No %s, not running ldconfig' % conf)
+def check_overlap(artifact, constituents, lac): #pragma: no cover
+ # check whether strata overlap
+ installed = defaultdict(set)
+ for dep in constituents:
+ handle = lac.get(dep)
+ tar = tarfile.open(fileobj=handle)
+ for member in tar.getmembers():
+ if member.type is not tarfile.DIRTYPE:
+ installed[member.name].add(dep)
+ tar.close()
+ handle.close()
+ overlaps = defaultdict(set)
+ for filename, artifacts in installed.iteritems():
+ if len(artifacts) > 1:
+ overlaps[frozenset(artifacts)].add(filename)
+ if len(overlaps) > 0:
+ logging.warning('Overlaps in artifact %s detected' % artifact.name)
+ for overlapping, files in sorted(overlaps.iteritems()):
+ logging.warning(' Artifacts %s overlap with files:' %
+ ', '.join(sorted(a.name for a in overlapping))
+ )
+ for filename in sorted(files):
+ logging.warning(' %s' % filename)
+
+
class BuilderBase(object):
'''Base class for building artifacts.'''
@@ -319,15 +346,21 @@ class StratumBuilder(BuilderBase):
for dependency in self.artifact.dependencies
if dependency.source.morphology['kind'] == 'chunk']
with self.build_watch('unpack-chunks'):
+ # download the chunk artifact if necessary
for chunk_artifact in constituents:
- # download the chunk artifact if necessary
if not self.local_artifact_cache.has(chunk_artifact):
source = self.remote_artifact_cache.get(chunk_artifact)
target = self.local_artifact_cache.put(chunk_artifact)
shutil.copyfileobj(source, target)
target.close()
source.close()
- # unpack it from the local artifact cache
+
+ # check for chunk overlaps
+ check_overlap(self.artifact, constituents,
+ self.local_artifact_cache)
+
+ # unpack it from the local artifact cache
+ for chunk_artifact in constituents:
logging.debug('unpacking chunk %s into stratum %s' %
(chunk_artifact.basename(),
self.artifact.basename()))
@@ -430,15 +463,21 @@ class SystemBuilder(BuilderBase): # pragma: no cover
def _unpack_strata(self, path):
logging.debug('Unpacking strata to %s' % path)
with self.build_watch('unpack-strata'):
+ # download the stratum artifact if necessary
for stratum_artifact in self.artifact.dependencies:
- # download the stratum artifact if necessary
if not self.local_artifact_cache.has(stratum_artifact):
source = self.remote_artifact_cache.get(stratum_artifact)
target = self.local_artifact_cache.put(stratum_artifact)
shutil.copyfileobj(source, target)
target.close()
source.close()
- # unpack it from the local artifact cache
+
+ # check whether the strata overlap
+ check_overlap(self.artifact, self.artifact.dependencies,
+ self.local_artifact_cache)
+
+ # unpack it from the local artifact cache
+ for stratum_artifact in self.artifact.dependencies:
f = self.local_artifact_cache.get(stratum_artifact)
morphlib.bins.unpack_binary_from_file(f, path)
f.close()
diff --git a/morphlib/stagingarea.py b/morphlib/stagingarea.py
index 8c84ac36..e4d233f0 100644
--- a/morphlib/stagingarea.py
+++ b/morphlib/stagingarea.py
@@ -14,12 +14,9 @@
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-import errno
import logging
import os
import shutil
-import stat
-import tarfile
import morphlib
@@ -91,86 +88,8 @@ class StagingArea(object):
'''
logging.debug('Installing artifact %s' %
- getattr(handle, 'name', 'unknown name'))
- tf = tarfile.open(fileobj=handle)
-
- # This is evil, but necessary. For some reason Python's system
- # call wrappers (os.mknod and such) do not (always?) set the
- # filename attribute of the OSError exception they raise. We
- # fix that by monkey patching the tf instance with wrappers
- # for the relevant methods to add things. The wrapper further
- # ignores EEXIST errors, since we do not (currently!) care about
- # overwriting files.
-
- def follow_symlink(path): # pragma: no cover
- try:
- return os.stat(path)
- except OSError:
- return None
-
- def prepare_extract(tarinfo, targetpath): # pragma: no cover
- '''Prepare to extract a tar file member onto targetpath?
-
- If the target already exist, and we can live with it or
- remove it, we do so. Otherwise, raise an error.
-
- It's OK to extract if:
-
- * the target does not exist
- * the member is a directory a directory and the
- target is a directory or a symlink to a directory
- (just extract, no need to remove)
- * the member is not a directory, and the target is not a directory
- or a symlink to a directory (remove target, then extract)
-
- '''
-
- try:
- existing = os.lstat(targetpath)
- except OSError:
- return True # target does not exist
-
- if tarinfo.isdir():
- if stat.S_ISDIR(existing.st_mode):
- return True
- elif stat.S_ISLNK(existing.st_mode):
- st = follow_symlink(targetpath)
- return st and stat.S_ISDIR(st.st_mode)
- else:
- if stat.S_ISDIR(existing.st_mode):
- return False
- elif stat.S_ISLNK(existing.st_mode):
- st = follow_symlink(targetpath)
- if st and not stat.S_ISDIR(st.st_mode):
- os.remove(targetpath)
- return True
- else:
- os.remove(targetpath)
- return True
- return False
-
- def monkey_patcher(real):
- def make_something(tarinfo, targetpath): # pragma: no cover
- prepare_extract(tarinfo, targetpath)
- try:
- return real(tarinfo, targetpath)
- except OSError, e:
- if e.errno != errno.EEXIST:
- if e.filename is None:
- e.filename = targetpath
- raise e
- else:
- raise
- return make_something
-
- tf.makedir = monkey_patcher(tf.makedir)
- tf.makefile = monkey_patcher(tf.makefile)
- tf.makeunknown = monkey_patcher(tf.makeunknown)
- tf.makefifo = monkey_patcher(tf.makefifo)
- tf.makedev = monkey_patcher(tf.makedev)
- tf.makelink = monkey_patcher(tf.makelink)
-
- tf.extractall(path=self.dirname)
+ getattr(handle, 'name', 'unknown name'))
+ morphlib.bins.unpack_binary_from_file(handle, self.dirname)
def remove(self):
'''Remove the entire staging area.
diff --git a/scripts/list-tree b/scripts/list-tree
index 2b3a6aa9..a1e2e8cb 100755
--- a/scripts/list-tree
+++ b/scripts/list-tree
@@ -20,6 +20,26 @@
set -eu
+shorttype(){
+ case "$*" in
+ "directory")
+ echo d
+ ;;
+ "regular file"|"regular empty file")
+ echo f
+ ;;
+ "symbolic link")
+ echo l
+ ;;
+ *)
+ echo "$*" >&2
+ echo U
+ ;;
+ esac
+}
+
export LC_ALL=C
cd "$1"
-find -printf '%y %p\n' | sort
+find | while read file; do
+ printf "%s %s\n" "$(shorttype $(stat -c %F $file))" "$file";
+done | sort
diff --git a/tests.as-root/make-patch.script b/tests.as-root/make-patch.script
index 0151ef6c..172f3e07 100755
--- a/tests.as-root/make-patch.script
+++ b/tests.as-root/make-patch.script
@@ -19,25 +19,6 @@
set -eu
-# Make a dummy kernel chunk.
-mkdir "$DATADIR/kernel-repo"
-cat <<EOF > "$DATADIR/kernel-repo/linux.morph"
-{
- "name": "linux",
- "kind": "chunk",
- "install-commands": [
- "mkdir -p \"\$DESTDIR/boot\"",
- "touch \"\$DESTDIR\"/extlinux.conf",
- "touch \"\$DESTDIR\"/boot/vmlinuz",
- "touch \"\$DESTDIR\"/boot/System.map"
- ]
-}
-EOF
-"$SRCDIR/scripts/run-git-in" "$DATADIR/kernel-repo" init --quiet
-"$SRCDIR/scripts/run-git-in" "$DATADIR/kernel-repo" add .
-"$SRCDIR/scripts/run-git-in" "$DATADIR/kernel-repo" commit --quiet -m foo \
- > /dev/null
-
# Make a stratum that include hello and kernel chunks.
cat <<EOF > "$DATADIR/morphs-repo/hello-stratum.morph"
{
diff --git a/tests.as-root/setup b/tests.as-root/setup
index 0b78ef9f..59e4a944 100755
--- a/tests.as-root/setup
+++ b/tests.as-root/setup
@@ -115,6 +115,24 @@ git add hello-system.morph
git commit --quiet -m "add morphs"
+# Make a dummy kernel chunk.
+mkdir "$DATADIR/kernel-repo"
+cat <<EOF > "$DATADIR/kernel-repo/linux.morph"
+{
+ "name": "linux",
+ "kind": "chunk",
+ "install-commands": [
+ "mkdir -p \"\$DESTDIR/boot\"",
+ "touch \"\$DESTDIR\"/extlinux.conf",
+ "touch \"\$DESTDIR\"/boot/vmlinuz",
+ "touch \"\$DESTDIR\"/boot/System.map"
+ ]
+}
+EOF
+"$SRCDIR/scripts/run-git-in" "$DATADIR/kernel-repo" init --quiet
+"$SRCDIR/scripts/run-git-in" "$DATADIR/kernel-repo" add .
+"$SRCDIR/scripts/run-git-in" "$DATADIR/kernel-repo" commit --quiet -m foo \
+ > /dev/null
# Create a morph configuration file.
cat <<EOF > "$DATADIR/morph.conf"
diff --git a/tests.as-root/system-overlap.script b/tests.as-root/system-overlap.script
new file mode 100755
index 00000000..111b236f
--- /dev/null
+++ b/tests.as-root/system-overlap.script
@@ -0,0 +1,123 @@
+#!/bin/sh
+#
+# If a system has multiple strata that have the same files in them,
+# then this should be notified
+#
+# Copyright (C) 2011, 2012 Codethink Limited
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; version 2 of the License.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+set -eu
+
+cache="$DATADIR/cache/artifacts"
+chunkrepo="$DATADIR/chunk-repo"
+morphsrepo="$DATADIR/morphs-repo"
+log="$DATADIR/morph.log"
+
+cd "$morphsrepo"
+git checkout --quiet -b overlap master
+cat <<EOF >overlap-system.morph
+{
+ "name": "overlap-system",
+ "kind": "system",
+ "disk-size": "1G",
+ "strata": [
+ "foo-baz-stratum",
+ "foo-barqux-stratum"
+ ]
+}
+EOF
+cat <<EOF >foo-baz-stratum.morph
+{
+ "name": "foo-baz-stratum",
+ "kind": "stratum",
+ "sources": [
+ {
+ "name": "overlap-foo-baz",
+ "repo": "test:chunk-repo",
+ "ref": "overlap"
+ },
+ {
+ "name": "linux",
+ "repo": "test:kernel-repo",
+ "ref": "master"
+ }
+ ]
+}
+EOF
+cat <<EOF >foo-barqux-stratum.morph
+{
+ "name": "foo-barqux-stratum",
+ "kind": "stratum",
+ "sources": [
+ {
+ "name": "overlap-foobar",
+ "repo": "test:chunk-repo",
+ "ref": "overlap"
+ },
+ {
+ "name": "overlap-fooqux",
+ "repo": "test:chunk-repo",
+ "ref": "overlap"
+ }
+ ]
+}
+EOF
+git add overlap-system.morph foo-baz-stratum.morph foo-barqux-stratum.morph
+git commit --quiet -m "add overlapping system"
+
+cd "$chunkrepo"
+git checkout --quiet -b overlap master
+cat <<EOF >overlap-foo-baz.morph
+{
+ "name": "overlap-foo-baz",
+ "kind": "chunk",
+ "install-commands": [
+ "mkdir -p \$DESTDIR/bin",
+ "for f in foo bar baz; do echo echo \$f >\$DESTDIR/bin/\$f; done"
+ ]
+}
+EOF
+
+cat <<EOF >overlap-foobar.morph
+{
+ "name": "overlap-foobar",
+ "kind": "chunk",
+ "install-commands": [
+ "mkdir -p \$DESTDIR/usr/bin \$DESTDIR/bin",
+ "echo echo foobar >\$DESTDIR/usr/bin/foobar",
+ "ln -s /usr/bin/foobar \$DESTDIR/bin/foo",
+ "ln -s /usr/bin/foobar \$DESTDIR/bin/bar"
+ ]
+}
+EOF
+
+cat <<EOF >overlap-fooqux.morph
+{
+ "name": "overlap-fooqux",
+ "kind": "chunk",
+ "install-commands": [
+ "mkdir -p \$DESTDIR/usr/bin \$DESTDIR/bin",
+ "for f in qux fooqux; do echo echo \$f >\$DESTDIR/usr/bin/\$f; done",
+ "ln -s /usr/bin/fooqux \$DESTDIR/bin/foo"
+ ]
+}
+EOF
+git add overlap-*.morph
+
+git commit --quiet -m 'Add overlapping chunks'
+
+"$SRCDIR/scripts/test-morph" \
+ build --log=$log test:morphs-repo overlap overlap-system.morph
+grep "WARNING\s" $log | sed 's/^.*WARNING/WARNING/'
diff --git a/tests.as-root/system-overlap.stdout b/tests.as-root/system-overlap.stdout
new file mode 100644
index 00000000..e43cb6a5
--- /dev/null
+++ b/tests.as-root/system-overlap.stdout
@@ -0,0 +1,7 @@
+WARNING Overlaps in artifact foo-barqux-stratum detected
+WARNING Artifacts overlap-foobar, overlap-fooqux overlap with files:
+WARNING bin/foo
+WARNING Overlaps in artifact overlap-system detected
+WARNING Artifacts foo-barqux-stratum, foo-baz-stratum overlap with files:
+WARNING ./bin/bar
+WARNING ./bin/foo
diff --git a/tests/stratum-overlap-keeps-directory-links.script b/tests/stratum-overlap-keeps-directory-links.script
new file mode 100755
index 00000000..a995194f
--- /dev/null
+++ b/tests/stratum-overlap-keeps-directory-links.script
@@ -0,0 +1,32 @@
+#!/bin/sh
+#
+# If a stratum has symbolic links pointing to files, then the symlink
+# should be removed and replaced by that file
+#
+# Copyright (C) 2011, 2012 Codethink Limited
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; version 2 of the License.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+set -eu
+
+cache="$DATADIR/cache/artifacts"
+extracted="$DATADIR/extracted"
+mkdir -p "$extracted"
+
+"$SRCDIR/scripts/test-morph" \
+ build test:morphs-repo overlap overlap-stratum.morph
+for f in "$cache"/*.stratum.overlap-stratum; do
+ tar -xf "$f" -C "$extracted"
+done
+test -h "$extracted/usr"
diff --git a/tests/stratum-overlap-keeps-directory-links.setup b/tests/stratum-overlap-keeps-directory-links.setup
new file mode 120000
index 00000000..255e9a74
--- /dev/null
+++ b/tests/stratum-overlap-keeps-directory-links.setup
@@ -0,0 +1 @@
+stratum-overlap-warns.setup \ No newline at end of file
diff --git a/tests/stratum-overlap-stomps-file-links.script b/tests/stratum-overlap-stomps-file-links.script
new file mode 100755
index 00000000..0d429bbf
--- /dev/null
+++ b/tests/stratum-overlap-stomps-file-links.script
@@ -0,0 +1,32 @@
+#!/bin/sh
+#
+# If a stratum has symbolic links pointing to files, then the symlink
+# should be removed and replaced by that file
+#
+# Copyright (C) 2011, 2012 Codethink Limited
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; version 2 of the License.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+set -eu
+
+cache="$DATADIR/cache/artifacts"
+extracted="$DATADIR/extracted"
+mkdir -p "$extracted"
+
+"$SRCDIR/scripts/test-morph" \
+ build test:morphs-repo overlap overlap-stratum.morph
+for f in "$cache"/*.stratum.overlap-stratum; do
+ tar -xf "$f" -C "$extracted"
+done
+test -f "$extracted/bin/foo"
diff --git a/tests/stratum-overlap-stomps-file-links.setup b/tests/stratum-overlap-stomps-file-links.setup
new file mode 120000
index 00000000..255e9a74
--- /dev/null
+++ b/tests/stratum-overlap-stomps-file-links.setup
@@ -0,0 +1 @@
+stratum-overlap-warns.setup \ No newline at end of file
diff --git a/tests/stratum-overlap-warns.script b/tests/stratum-overlap-warns.script
new file mode 100755
index 00000000..edb1d855
--- /dev/null
+++ b/tests/stratum-overlap-warns.script
@@ -0,0 +1,28 @@
+#!/bin/sh
+#
+# If a stratum has multiple chunks that have the same files in them,
+# then this should be notified
+#
+# Copyright (C) 2011, 2012 Codethink Limited
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; version 2 of the License.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+set -eu
+
+log="$DATADIR/morph.log"
+cache="$DATADIR/cache/artifacts"
+
+"$SRCDIR/scripts/test-morph" \
+ build --log=$log test:morphs-repo overlap overlap-stratum.morph
+grep WARNING $log | sed 's/^.*WARNING/WARNING/'
diff --git a/tests/stratum-overlap-warns.setup b/tests/stratum-overlap-warns.setup
new file mode 100755
index 00000000..94aed05a
--- /dev/null
+++ b/tests/stratum-overlap-warns.setup
@@ -0,0 +1,113 @@
+#!/bin/sh
+#
+# If a stratum has multiple chunks that have the same files in them,
+# then this should be notified
+#
+# Copyright (C) 2011, 2012 Codethink Limited
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; version 2 of the License.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+set -eu
+
+chunkrepo="$DATADIR/chunk-repo"
+morphsrepo="$DATADIR/morphs-repo"
+
+cd "$morphsrepo"
+git checkout --quiet -b overlap master
+cat <<EOF >overlap-stratum.morph
+{
+ "name": "overlap-stratum",
+ "kind": "stratum",
+ "sources": [
+ {
+ "name": "dirs",
+ "repo": "test:chunk-repo",
+ "ref": "overlap"
+ },
+ {
+ "name": "overlap-foobar",
+ "repo": "test:chunk-repo",
+ "ref": "overlap"
+ },
+ {
+ "name": "overlap-fooqux",
+ "repo": "test:chunk-repo",
+ "ref": "overlap"
+ },
+ {
+ "name": "overlap-foo-baz",
+ "repo": "test:chunk-repo",
+ "ref": "overlap"
+ }
+ ]
+}
+EOF
+git add overlap-stratum.morph
+git commit --quiet -m "add overlapping stratum"
+
+cd "$chunkrepo"
+git checkout --quiet -b overlap master
+
+cat <<EOF >dirs.morph
+{
+ "name": "dirs",
+ "kind": "chunk",
+ "install-commands": [
+ "mkdir -p \$DESTDIR/bin",
+ "ln -s / \$DESTDIR/usr"
+ ]
+}
+EOF
+git add dirs.morph
+
+cat <<EOF >overlap-foo-baz.morph
+{
+ "name": "overlap-foo-baz",
+ "kind": "chunk",
+ "install-commands": [
+ "mkdir -p \$DESTDIR/bin",
+ "for f in foo bar baz; do echo echo \$f >\$DESTDIR/bin/\$f; done"
+ ]
+}
+EOF
+git add overlap-foo-baz.morph
+
+cat <<EOF >overlap-foobar.morph
+{
+ "name": "overlap-foobar",
+ "kind": "chunk",
+ "install-commands": [
+ "mkdir -p \$DESTDIR/usr/bin \$DESTDIR/bin",
+ "echo echo foobar >\$DESTDIR/usr/bin/foobar",
+ "ln -s /usr/bin/foobar \$DESTDIR/bin/foo",
+ "ln -s /usr/bin/foobar \$DESTDIR/bin/bar"
+ ]
+}
+EOF
+git add overlap-foobar.morph
+
+cat <<EOF >overlap-fooqux.morph
+{
+ "name": "overlap-fooqux",
+ "kind": "chunk",
+ "install-commands": [
+ "mkdir -p \$DESTDIR/usr/bin \$DESTDIR/bin",
+ "for f in qux fooqux; do echo echo \$f >\$DESTDIR/usr/bin/\$f; done",
+ "ln -s /usr/bin/fooqux \$DESTDIR/bin/foo"
+ ]
+}
+EOF
+git add overlap-fooqux.morph
+
+git commit --quiet -m 'Add overlapping chunks'
diff --git a/tests/stratum-overlap-warns.stdout b/tests/stratum-overlap-warns.stdout
new file mode 100644
index 00000000..eaddf9c1
--- /dev/null
+++ b/tests/stratum-overlap-warns.stdout
@@ -0,0 +1,5 @@
+WARNING Overlaps in artifact overlap-stratum detected
+WARNING Artifacts overlap-foo-baz, overlap-foobar overlap with files:
+WARNING bin/bar
+WARNING Artifacts overlap-foo-baz, overlap-foobar, overlap-fooqux overlap with files:
+WARNING bin/foo