catch up fix
authorRichard Taylor <richard.i.taylor@nokia.com>
Thu, 18 Mar 2010 14:00:53 +0000
branchfix
changeset 379 59ec73480ed3
parent 378 169ad30bb410 (diff)
parent 364 f4ccb4c573da (current diff)
child 386 8c80a05c93b7
catch up
sbsv2/raptor/RELEASE-NOTES.txt
--- a/sbsv2/raptor/RELEASE-NOTES.txt	Tue Mar 16 17:04:08 2010 +0000
+++ b/sbsv2/raptor/RELEASE-NOTES.txt	Thu Mar 18 14:00:53 2010 +0000
@@ -9,6 +9,8 @@
 - SF Bug 2191 - [Raptor] - When forcesuccess is enabled, exit status for a failed recipe is "retry" but should be "failed"
 - Fix: extend tracecompiler tests to Linux
 - Fix: Amendment to SF Bug 1511 fix - removal of blanked DEFFILE keyword from e32abiv2ani.flm
+- Fix: improve robustness to bad -c options
+
 
 version 2.12.4
 
--- a/sbsv2/raptor/lib/config/locations.xml	Tue Mar 16 17:04:08 2010 +0000
+++ b/sbsv2/raptor/lib/config/locations.xml	Thu Mar 18 14:00:53 2010 +0000
@@ -30,7 +30,7 @@
 		<env name='SBS_GNUTOUCH' default='/bin/touch' type='tool'/>
 		<env name='SBS_GNUFIND' default='/usr/bin/find' type='tool'/>
 		<env name='SBS_GNUGREP' default='/bin/grep' type='tool'/>
-		<env name='SBS_GNUSORT' default='/bin/sort' type='tool'/>
+		<env name='SBS_GNUSORT' default='sort' type='tool'/>
 		<env name='SBS_SHELL' default="$(SBS_HOME)/$(HOSTPLATFORM_DIR)/bin/sh" type='tool'/>
 		<env name='SBS_ZIP' default="$(SBS_HOME)/$(HOSTPLATFORM_DIR)/bin/zip" type='tool'/>
 		<env name='SBS_UNZIP' default="$(SBS_HOME)/$(HOSTPLATFORM_DIR)/bin/unzip" type='tool'/>
--- a/sbsv2/raptor/python/plugins/filter_terminal.py	Tue Mar 16 17:04:08 2010 +0000
+++ b/sbsv2/raptor/python/plugins/filter_terminal.py	Thu Mar 18 14:00:53 2010 +0000
@@ -161,7 +161,7 @@
 
 		# list of strings to catch recipe warnings (must be lowercase)
 		self.recipe_warning_expr = ["warning:"]
-
+		
 	def isMakeWarning(self, text):
                 """A simple test for warnings.
                 Can be extended do to more comprehensive checking."""
@@ -201,6 +201,9 @@
 		if self.raptor.quiet:
 			self.quiet = True
 		
+		# the build configurations which were reported
+		self.built_configs = []
+		
 		# keep count of errors and warnings
 		self.err_count = 0
 		self.warn_count = 0
@@ -393,7 +396,9 @@
 				self.recipeBody.append(text)
 			else:
 				self.recipelineExceeded += 1
-
+		elif text.startswith("<info>Buildable configuration '"):
+			# <info>Buildable configuration 'name'</info>
+			self.built_configs.append(text[30:-8])
 
 	def logit(self):
 		""" log a message """
@@ -435,8 +440,11 @@
 			sys.stdout.write("%s : errors: %s\n" % (raptor.name,
 					self.err_count))
 		else:
-			sys.stdout.write("\nno warnings or errors\n")
+			sys.stdout.write("\nno warnings or errors\n\n")
 
+		for bc in self.built_configs:
+			sys.stdout.write("built " + bc + "\n")
+			
 		sys.stdout.write("\nRun time %d seconds\n" % self.raptor.runtime);
 		sys.stdout.write("\n")
 		return True
--- a/sbsv2/raptor/python/raptor.py	Tue Mar 16 17:04:08 2010 +0000
+++ b/sbsv2/raptor/python/raptor.py	Thu Mar 18 14:00:53 2010 +0000
@@ -42,7 +42,6 @@
 import sys
 import types
 import time
-import re
 import traceback
 import pluginbox
 from xml.sax.saxutils import escape
@@ -232,8 +231,9 @@
 
 	def makefile(self, makefilename_base, engine, named = False):
 		"""Makefiles for individual mmps not feasible at the moment"""
-		pass # Cannot, currently, "unfurl an mmp" directly but do want
-		     # to be able to simulate the overall recursive unfurling of a build.
+		pass 
+		# Cannot, currently, "unfurl an mmp" directly but do want
+		# to be able to simulate the overall recursive unfurling of a build.
 
 class Component(ModelNode):
 	"""A group of projects or, in symbian-speak, a bld.inf.
@@ -321,7 +321,6 @@
 
 		# insert the start time into the Makefile name?
 
-		buildconfig = build.GetConfig("build").GenerateBuildUnits(build.cache)
 		self.configs = build.buildUnitsToBuild
 
 		# Pass certain CLI flags through to the makefile-generating sbs calls
@@ -385,8 +384,7 @@
 			makefile_path = str(build.topMakefile) + "_" + str(loop_number)
 			try:
 				os.unlink(makefile_path) # until we have dependencies working properly
-			except Exception,e:
-				# print "couldn't unlink %s: %s" %(componentMakefileName, str(e))
+			except Exception:
 				pass
 
 			# add some basic data in a component-wide variant
@@ -859,32 +857,6 @@
 
 		self.cache.Load(self.systemFLM)
 
-	def GetConfig(self, configname):
-		names = configname.split(".")
-
-		cache = self.cache
-
-		base = names[0]
-		mods = names[1:]
-
-		if base in cache.groups:
-			x = cache.FindNamedGroup(base)
-		elif base in cache.aliases:
-			x = cache.FindNamedAlias(base)
-		elif base in cache.variants:
-			x = cache.FindNamedVariant(base)
-		else:
-			raise Exception("Unknown build configuration '%s'" % configname)
-
-		x.ClearModifiers()
-
-
-		try:
-			for m in mods: x.AddModifier( cache.FindNamedVariant(m) )
-		except KeyError:
-			raise Exception("Unknown build configuration '%s'" % configname)
-		return x
-
 	def GetBuildUnitsToBuild(self, configNames):
 		"""Return a list of the configuration objects that correspond to the
 		   list of configuration names in the configNames parameter.
@@ -900,17 +872,7 @@
 			else:
 				configNames.append(self.defaultConfig)
 
-		buildUnitsToBuild = set()
-
-
-		for c in set(configNames):
-			self.Debug("BuildUnit: %s", c)
-			try:
-				x = self.GetConfig(c)
-				gb = x.GenerateBuildUnits(self.cache)
-				buildUnitsToBuild.update( gb )
-			except Exception, e:
-				self.FatalError(str(e))
+		buildUnitsToBuild = raptor_data.GetBuildUnits(configNames, self.cache, self)
 
 		for b in buildUnitsToBuild:
 			self.Info("Buildable configuration '%s'", b.name)
@@ -1003,7 +965,6 @@
 			dir = generic_path.Path(aDir)
 
 		bldInf = dir.Append(self.buildInformation)
-		componentgroup = []
 
 		if bldInf.isFile():
 			return bldInf
@@ -1150,7 +1111,7 @@
 				self.out.write(raptor_timing.Timing.discovery_string(object_type = object_type,
 						count = count))
 			except Exception, exception:
-				Error(exception.Text, function = "InfoDiscoveryTime")
+				self.Error(exception.Text, function = "InfoDiscoveryTime")
 
 	def InfoStartTime(self, object_type, task, key):
 		if self.timing:
@@ -1158,7 +1119,7 @@
 				self.out.write(raptor_timing.Timing.start_string(object_type = object_type,
 						task = task, key = key))
 			except Exception, exception:
-				Error(exception.Text, function = "InfoStartTime")
+				self.Error(exception.Text, function = "InfoStartTime")
 
 	def InfoEndTime(self, object_type, task, key):
 		if self.timing:
@@ -1166,7 +1127,7 @@
 				self.out.write(raptor_timing.Timing.end_string(object_type = object_type,
 						task = task, key = key))
 			except Exception, exception:
-				Error(exception.Text, function = "InfoEndTime")
+				self.Error(exception.Text, function = "InfoEndTime")
 
 	def Debug(self, format, *extras, **attributes):
 		"Send a debugging message to the configured channel"
@@ -1280,6 +1241,9 @@
 			self.AssertBuildOK()
 			buildUnitsToBuild = self.GetBuildUnitsToBuild(self.configNames)
 
+			if len(buildUnitsToBuild) == 0:
+				raise BuildCannotProgressException("No configurations to build.")
+			
 			self.buildUnitsToBuild = buildUnitsToBuild
 
 			# find out what components to build, and in what way
--- a/sbsv2/raptor/python/raptor_data.py	Tue Mar 16 17:04:08 2010 +0000
+++ b/sbsv2/raptor/python/raptor_data.py	Thu Mar 18 14:00:53 2010 +0000
@@ -65,7 +65,7 @@
 
 # Make sure not to start up on an unsupported platform
 if not HostPlatform.IsKnown(HostPlatform.hostplatform):
-	raise Exception("raptor_data module loaded on an unrecognised platform '%s'. Expected one of %s" % (hostplatform, str(hostplatforms)))
+	raise Exception("raptor_data module loaded on an unrecognised platform '%s'. Expected one of %s" % (HostPlatform.hostplatform, str(HostPlatform.hostplatforms)))
 
 
 # raptor_data module classes
@@ -139,7 +139,13 @@
 		raise BadReferenceError()
 
 	def GetModifiers(self, cache):
-		return [ cache.FindNamedVariant(m) for m in self.modifiers ]
+		mods = []
+		for m in self.modifiers:
+			try:
+				mods.append(cache.FindNamedVariant(m))
+			except KeyError:
+				raise BadReferenceError(m)
+		return mods
 
 	def Valid(self):
 		return self.ref
@@ -710,7 +716,7 @@
 
 	def __str__(self):
 		attributes = "name='" + self.name + "' type='" + self.type + "'"
-		if default != None:
+		if self.default != None:
 			attributes += " default='" + self.default + "'"
 
 		if type == "tool":
@@ -906,7 +912,6 @@
 		s += "</var>"
 		return s
 
-import traceback
 class VariantRef(Reference):
 
 	def __init__(self, ref=None):
@@ -918,7 +923,7 @@
 	def Resolve(self, cache):
 		try:
 			return cache.FindNamedVariant(self.ref)
-		except KeyError, e:
+		except KeyError:
 			raise BadReferenceError(self.ref)
 
 class MissingVariantException(Exception):
@@ -961,7 +966,7 @@
 					missing_variants.append(r.ref)
 				
 			if len(missing_variants) > 0:
-				raise MissingVariantException("Missing variants '%s'", " ".join(missing_variants))
+				raise MissingVariantException("Missing variants '%s'" % " ".join(missing_variants))
 
 	def GenerateBuildUnits(self, cache):
 		self.Resolve(cache)
@@ -1026,25 +1031,27 @@
 
 	def GenerateBuildUnits(self, cache):
 		units = []
-
+		
 		missing_variants = []
 		for r in self.childRefs:
-			refMods = r.GetModifiers(cache)
-
 			try:
 				obj = r.Resolve(cache=cache)
 			except BadReferenceError:
 				missing_variants.append(r.ref)
 			else:
 				obj.ClearModifiers()
+				try:
+					refMods = r.GetModifiers(cache)
+				except BadReferenceError,e:
+					missing_variants.append(str(e))
+				else:
+					for m in refMods + self.modifiers:
+						obj.AddModifier(m)
 
-				for m in refMods + self.modifiers:
-					obj.AddModifier(m)
-
-				units.extend( obj.GenerateBuildUnits(cache) )
+					units.extend( obj.GenerateBuildUnits(cache) )
 
 		if len(missing_variants) > 0:
-			raise MissingVariantException("Missing variants '%s'", " ".join(missing_variants))
+			raise MissingVariantException("Missing variants '%s'" % " ".join(missing_variants))
 
 		return units
 
@@ -1055,7 +1062,7 @@
 		Reference.__init__(self, ref)
 
 	def __str__(self):
-		return "<%s /><groupRef ref='%s' mod='%s'/>" % (prefix, self.ref, ".".join(self.modifiers))
+		return "<groupRef ref='%s' mod='%s'/>" % (self.ref, ".".join(self.modifiers))
 
 	def Resolve(self, cache):
 		try:
@@ -1063,6 +1070,81 @@
 		except KeyError:
 			raise BadReferenceError(self.ref)
 
+def GetBuildUnits(configNames, cache, logger):
+	"""expand a list of config strings like "arm.v5.urel" into a list
+	of BuildUnit objects that can be queried for settings.
+	
+	The expansion tries to be tolerant of errors in the XML so that a
+	typo in one part of a group does not invalidate the whole group.
+	"""
+	
+	# turn dot-separated name strings into Model objects (Group, Alias, Variant)
+	models = []
+		
+	for c in set(configNames):
+		ok = True
+		names = c.split(".")
+
+		base = names[0]
+		mods = names[1:]
+
+		if base in cache.groups:
+			x = cache.FindNamedGroup(base)
+		elif base in cache.aliases:
+			x = cache.FindNamedAlias(base)
+		elif base in cache.variants:
+			x = cache.FindNamedVariant(base)
+		else:
+			logger.Error("Unknown build configuration '%s'" % base)
+			continue
+
+		x.ClearModifiers()
+
+		for m in mods:
+			if m in cache.variants:
+				x.AddModifier( cache.FindNamedVariant(m) )
+			else:
+				logger.Error("Unknown build variant '%s'" % m)
+				ok = False
+				
+		if ok:
+			models.append(copy.copy(x))
+
+	# turn Model objects into BuildUnit objects
+	#
+	# all objects have a GenerateBuildUnits method but don't use
+	# that for Groups because it is not tolerant of errors (the
+	# first error raises an exception and the rest of the group is
+	# abandoned)
+	units = []
+		
+	while len(models) > 0:
+		x = models.pop()
+		try:
+			if isinstance(x, (Alias, Variant)):
+				# these we just turn straight into BuildUnits
+				units.extend(x.GenerateBuildUnits(cache))
+			elif isinstance(x, Group):
+				# deal with each part of the group separately (later)
+				for child in x.childRefs:
+					modChild = copy.copy(child)
+					modChild.modifiers = child.modifiers + [m.name for m in x.modifiers]
+					models.append(modChild)
+			elif isinstance(x, Reference):
+				# resolve references and their modifiers
+				try:
+					obj = x.Resolve(cache)
+					modObj = copy.copy(obj)
+					modObj.modifiers = x.GetModifiers(cache)
+				except BadReferenceError,e:
+					logger.Error("Unknown reference '%s'" % str(e))
+				else:
+					models.append(modObj)
+		except Exception, e:
+			logger.Error(str(e))
+
+	return units
+	
 class ToolErrorException(Exception):
 	def __init__(self, s):
 		Exception.__init__(self,s)
@@ -1364,6 +1446,9 @@
 class UninitialisedVariableException(Exception):
 	pass
 
+class RecursionException(Exception):
+	pass
+
 class Evaluator(object):
 	"""Determine the values of variables under different Configurations.
 	Either of specification and buildUnit may be None."""
@@ -1436,7 +1521,6 @@
 			for k, v in self.dict.items():
 				if v.find('$(' + k + ')') != -1:
 						raise RecursionException("Recursion Detected in variable '%s' in configuration '%s' " % (k,configName))
-						expanded = "RECURSIVE_INVALID_STRING"
 				else:
 					expanded = self.ExpandAll(v, specName, configName)
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sbsv2/raptor/test/smoke_suite/keepgoing.py	Thu Mar 18 14:00:53 2010 +0000
@@ -0,0 +1,106 @@
+#
+# Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies).
+# All rights reserved.
+# This component and the accompanying materials are made available
+# under the terms of the License "Eclipse Public License v1.0"
+# which accompanies this distribution, and is available
+# at the URL "http://www.eclipse.org/legal/epl-v10.html".
+#
+# Initial Contributors:
+# Nokia Corporation - initial contribution.
+#
+# Contributors:
+#
+# Description: 
+#
+
+from raptor_tests import SmokeTest
+
+def run():
+	t = SmokeTest()
+	t.description = """Raptor should keep going and build as much as possible with the -k option specified."""
+	
+	command = "sbs -b smoke_suite/test_resources/simple/bld.inf -k"
+	config = " --configpath=test/smoke_suite/test_resources/keepgoing"
+	targets = [
+		"$(EPOCROOT)/epoc32/release/armv5/udeb/test.exe",
+		"$(EPOCROOT)/epoc32/release/armv5/udeb/test.exe.map",
+		"$(EPOCROOT)/epoc32/release/armv5/urel/test.exe",
+		"$(EPOCROOT)/epoc32/release/armv5/urel/test.exe.map",
+		"$(EPOCROOT)/epoc32/release/armv5/udeb/test.exe.sym",
+		"$(EPOCROOT)/epoc32/release/armv5/urel/test.exe.sym"
+		]	
+	buildtargets = [
+		"test_/armv5/udeb/test.o",
+		"test_/armv5/urel/test.o",
+		"test_/armv5/udeb/test.o.d",
+		"test_/armv5/udeb/test3.o.d",
+		"test_/armv5/udeb/test4.o.d",
+		"test_/armv5/udeb/test5.o.d",
+		"test_/armv5/udeb/test1.o.d",
+		"test_/armv5/udeb/test6.o.d",
+		"test_/armv5/udeb/test2.o.d",
+		"test_/armv5/udeb/test3.o",
+		"test_/armv5/udeb/test4.o",
+		"test_/armv5/udeb/test5.o",
+		"test_/armv5/udeb/test1.o",
+		"test_/armv5/udeb/test6.o",
+		"test_/armv5/udeb/test2.o",
+		"test_/armv5/urel/test.o.d",
+		"test_/armv5/urel/test3.o.d",
+		"test_/armv5/urel/test4.o.d",
+		"test_/armv5/urel/test5.o.d",
+		"test_/armv5/urel/test1.o.d",
+		"test_/armv5/urel/test6.o.d",
+		"test_/armv5/urel/test2.o.d",
+		"test_/armv5/urel/test3.o",
+		"test_/armv5/urel/test4.o",
+		"test_/armv5/urel/test5.o",
+		"test_/armv5/urel/test1.o",
+		"test_/armv5/urel/test6.o",
+		"test_/armv5/urel/test2.o",
+		"test_/armv5/udeb/test_udeb_objects.via",
+		"test_/armv5/urel/test_urel_objects.via"
+		]
+	
+	# using a non-existent config with -c should build any independent configs
+	t.id = "115a"
+	t.name = "keepgoing_bad_config"
+	t.command = command + " -c armv5 -c armv5.bogus"
+	t.targets = targets
+	t.addbuildtargets("smoke_suite/test_resources/simple/bld.inf", buildtargets)
+	t.mustmatch = ["sbs: error: Unknown build variant 'bogus'"]
+	t.warnings = 0
+	t.errors = 1
+	t.returncode = 1
+	t.run()
+	
+	# using groups with bad sub-groups should build any independent groups
+	t.id = "115b"
+	t.name = "keepgoing_bad_subgroup"
+	t.command = command + config + " -c lots_of_products"
+	t.mustmatch = ["Unknown reference 'qwertyuio'",
+	               "Unknown reference 'asdfghjkl'",
+	               "Unknown reference 'zxcvbnm_p'"]
+	t.warnings = 0
+	t.errors = 3
+	t.returncode = 1
+	t.run()
+	
+	# using groups with bad sub-sub-groups should build any independent groups
+	t.id = "115c"
+	t.name = "keepgoing_bad_subsubgroup"
+	t.command = command + config + " -c lots_of_products_2"
+	t.mustmatch = ["Unknown reference 'qwertyuio'",
+	               "Unknown reference 'asdfghjkl'",
+	               "Unknown reference 'zxcvbnm_p'"]
+	t.warnings = 0
+	t.errors = 3
+	t.returncode = 1
+	t.run()
+	
+	# summarise	
+	t.id = "115"
+	t.name = "keepgoing"
+	t.print_result()
+	return t
--- a/sbsv2/raptor/test/smoke_suite/parallel_parsing.py	Tue Mar 16 17:04:08 2010 +0000
+++ b/sbsv2/raptor/test/smoke_suite/parallel_parsing.py	Thu Mar 18 14:00:53 2010 +0000
@@ -54,7 +54,7 @@
 	warnings = 0
 		
 	t.id = "104"
-	t.name = "parallelparsing"
+	t.name = "parallel_parsing"
 	t.description = description
 	t.command = command 
 	t.targets = targets
--- a/sbsv2/raptor/test/smoke_suite/terminal_filter_tests.py	Tue Mar 16 17:04:08 2010 +0000
+++ b/sbsv2/raptor/test/smoke_suite/terminal_filter_tests.py	Thu Mar 18 14:00:53 2010 +0000
@@ -19,10 +19,10 @@
 def run():
 
 	t = SmokeTest()
-	t.id = "87"
-	t.name = "terminal_filter_tests"
-	t.description = "terminal_filter_tests: Tests the terminal filter against" \
-			+ " log files to ensure it 'does the right thing'"
+	t.description = "Tests against log files to ensure it 'does the right thing'"
+	
+	t.id = "87a"
+	t.name = "terminal_filter_tests_log"
 	t.command = "$(SBS_HOME)/test/smoke_suite/test_resources/refilter/testfilterterminal"
 	t.countmatch = [
 		# One of each type of error occurs early in the 'sbs' call where there
@@ -36,4 +36,19 @@
 	]
 	t.errors = 4
 	t.run()
+	
+	t.id = "87b"
+	t.name = "terminal_filter_tests_configs"
+	t.command = "sbs -b smoke_suite/test_resources/simple/bld.inf"
+	t.countmatch = []
+	t.errors = 0
+	t.mustmatch_singleline = ["built 'armv5_urel'",
+							  "built 'armv5_udeb'",
+							  "built 'winscw_urel'",
+							  "built 'winscw_udeb'" ]
+	t.run()
+	
+	t.id = "87"
+	t.name = "terminal_filter_tests"
+	t.print_result()
 	return t
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sbsv2/raptor/test/smoke_suite/test_resources/keepgoing/groups.xml	Thu Mar 18 14:00:53 2010 +0000
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+
+<build xmlns="http://symbian.com/xml/build"
+       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xsi:schemaLocation="http://symbian.com/xml/build build/2_0.xsd">
+
+  <!-- This group deliberately contains some non-existent products 
+  -->
+  <group name="lots_of_products">
+	<groupRef ref='armv5'/>
+	<groupRef ref='armv5' mod="qwertyuio"/>
+	<groupRef ref='armv5' mod="asdfghjkl"/>
+	<groupRef ref='armv5' mod="zxcvbnm_p"/>
+  </group>
+  
+  <!-- This group just contains the one above to check multiple levels
+  -->
+  <group name="lots_of_products_2">
+    <groupRef ref="lots_of_products"/>
+  </group>
+  
+</build>
--- a/sbsv2/raptor/test/unit_suite/raptor_data_unit.py	Tue Mar 16 17:04:08 2010 +0000
+++ b/sbsv2/raptor/test/unit_suite/raptor_data_unit.py	Thu Mar 18 14:00:53 2010 +0000
@@ -430,8 +430,105 @@
 		self.failUnless(self.checkForParam(p, "D", None))
 		f = extended.GetFLMIncludePath(cache)
 		self.assertEqual(f.File(), "base.flm")
+
+	def testGetBuildUnits(self):
+		r = raptor.Raptor()
+
+		# <group name="g1">
+		g1 = raptor_data.Group("g1")
+		r.cache.AddGroup(g1)
+		
+		# <groupRef ref="g2" mod="A.B"/>
+		g2a = raptor_data.GroupRef()
+		g2a.SetProperty("ref", "g2")
+		g2a.SetProperty("mod", "A.B")
+		g1.AddChild(g2a)
+		
+		# <groupRef ref="g2" mod="C.D"/>
+		g2b = raptor_data.GroupRef()
+		g2b.SetProperty("ref", "g2")
+		g2b.SetProperty("mod", "C.D")
+		g1.AddChild(g2b)
+		
+		# <group name="g2">
+		g2 = raptor_data.Group("g2")
+		r.cache.AddGroup(g2)
+		
+		# <varRef ref="V" mod="E.F"/>
+		v2 = raptor_data.VariantRef()
+		v2.SetProperty("ref", "V")
+		v2.SetProperty("mod", "E.F")
+		g2.AddChild(v2)
+		
+		# <varRef ref="V" mod="G.H"/>
+		v3 = raptor_data.VariantRef()
+		v3.SetProperty("ref", "V")
+		v3.SetProperty("mod", "G.H")
+		g2.AddChild(v3)
+		
+		# <aliasRef ref="X" mod="I.J"/>
+		v4 = raptor_data.AliasRef()
+		v4.SetProperty("ref", "X")
+		v4.SetProperty("mod", "I.J")
+		g2.AddChild(v4)
+		
+		# <aliasRef ref="X" mod="K.L"/>
+		v5 = raptor_data.AliasRef()
+		v5.SetProperty("ref", "X")
+		v5.SetProperty("mod", "K.L")
+		g2.AddChild(v5)
+		
+		r.cache.AddVariant(raptor_data.Variant("A"))
+		r.cache.AddVariant(raptor_data.Variant("B"))
+		r.cache.AddVariant(raptor_data.Variant("C"))
+		r.cache.AddVariant(raptor_data.Variant("D"))
+		r.cache.AddVariant(raptor_data.Variant("E"))
+		r.cache.AddVariant(raptor_data.Variant("F"))
+		r.cache.AddVariant(raptor_data.Variant("G"))
+		r.cache.AddVariant(raptor_data.Variant("H"))
+		r.cache.AddVariant(raptor_data.Variant("I"))
+		r.cache.AddVariant(raptor_data.Variant("J"))
+		r.cache.AddVariant(raptor_data.Variant("K"))
+		r.cache.AddVariant(raptor_data.Variant("L"))
+		
+		r.cache.AddVariant(raptor_data.Variant("V"))
+		
+		# <alias name="X" meaning="A.B.C.D.E.F.G.H/>
+		alias = raptor_data.Alias("X")
+		alias.SetProperty("meaning", "A.B.C.D.E.F.G.H")
+		r.cache.AddAlias(alias)
+
+		r.cache.AddVariant(raptor_data.Variant("Y"))
+		r.cache.AddVariant(raptor_data.Variant("Z"))
 	
-	
+		units = raptor_data.GetBuildUnits(["g1.Y", "g1.Z"], r.cache, r)
+		
+		# <group name="g1">
+		#   <groupRef ref="g2" mod="A.B"/>    g2.A.B
+		#   <groupRef ref="g2" mod="C.D"/>    g2.C.D
+		# </group>
+		# <group name="g2">
+		#   <varRef ref="V" mod="E.F"/>       V.E.F
+		#   <varRef ref="V" mod="G.H"/>       V.G.H
+		#   <aliasRef ref="X" mod="I.J"/>     X.I.J
+		#   <aliasRef ref="X" mod="K.L"/>     X.K.L
+		# </group>
+		# <alias name="X" meaning="A.B.C.D.E.F.G.H/>
+		#
+		expected = [ "VEFABY", "VGHABY", "ABCDEFGHIJABY", "ABCDEFGHKLABY",
+				     "VEFCDY", "VGHCDY", "ABCDEFGHIJCDY", "ABCDEFGHKLCDY",
+		             "VEFABZ", "VGHABZ", "ABCDEFGHIJABZ", "ABCDEFGHKLABZ",
+				     "VEFCDZ", "VGHCDZ", "ABCDEFGHIJCDZ", "ABCDEFGHKLCDZ" ]
+		
+		self.failUnlessEqual(len(units), len(expected))
+		
+		for u in units:
+			vars = "".join([v.name for v in u.variants])
+			self.failUnless(vars in expected, vars + " was not expected")
+			expected.remove(vars)
+		
+		self.failUnless(len(expected) == 0, str(expected) + " not found")
+		
 # run all the tests
 
 from raptor_tests import SmokeTest