Added new test framework

git-svn-id: https://asterixdb.googlecode.com/svn/branches/asterix_stabilization@764 eaa15691-b419-025a-1212-ee371bd00084
diff --git a/asterix-app/pom.xml b/asterix-app/pom.xml
index 75ec501..fe62ae9 100644
--- a/asterix-app/pom.xml
+++ b/asterix-app/pom.xml
@@ -214,6 +214,12 @@
                     <artifactId>cobertura</artifactId>
                     <version>1.9.4</version>
                 </dependency>
+                <dependency>
+                	<groupId>edu.uci.ics.asterix</groupId>
+                	<artifactId>asterix-test-framework</artifactId>
+                	<version>0.0.4-SNAPSHOT</version>
+                	<scope>test</scope>
+                </dependency>
 	</dependencies>
 
 </project>
diff --git a/asterix-app/src/test/resources/runtimets/queries/ifthenelse_01.aql b/asterix-app/src/test/resources/runtimets/queries/ifthenelse_01.aql
deleted file mode 100644
index 1da06a7..0000000
--- a/asterix-app/src/test/resources/runtimets/queries/ifthenelse_01.aql
+++ /dev/null
@@ -1,8 +0,0 @@
-use dataverse test;
-
-write output to nc1:"rttest/ifthenelse_01.adm";
-
-if (2>1) then
-    20
-else
-    10
diff --git a/asterix-app/src/test/resources/runtimets/queries/float_01.aql b/asterix-app/src/test/resources/runtimets/queries/misc/float_01.aql
similarity index 72%
rename from asterix-app/src/test/resources/runtimets/queries/float_01.aql
rename to asterix-app/src/test/resources/runtimets/queries/misc/float_01.aql
index dea3790..ec0e8e5 100644
--- a/asterix-app/src/test/resources/runtimets/queries/float_01.aql
+++ b/asterix-app/src/test/resources/runtimets/queries/misc/float_01.aql
@@ -2,7 +2,7 @@
 create dataverse test;
 use dataverse test;
 
-write output to nc1:"rttest/float_01.adm";
+write output to nc1:"rttest/misc_float_01.adm";
 
 for $f in [1f, 1F, 1.1f, 1.1F, .1f, .1F]
 return $f
diff --git a/asterix-app/src/test/resources/runtimets/queries/groupby-orderby-count.aql b/asterix-app/src/test/resources/runtimets/queries/misc/groupby-orderby-count.aql
similarity index 90%
rename from asterix-app/src/test/resources/runtimets/queries/groupby-orderby-count.aql
rename to asterix-app/src/test/resources/runtimets/queries/misc/groupby-orderby-count.aql
index ce4662d..e5887ae 100644
--- a/asterix-app/src/test/resources/runtimets/queries/groupby-orderby-count.aql
+++ b/asterix-app/src/test/resources/runtimets/queries/misc/groupby-orderby-count.aql
@@ -13,7 +13,7 @@
 using "edu.uci.ics.asterix.external.dataset.adapter.NCFileSystemAdapter"
 (("path"="nc1://data/twitter/extrasmalltweets.txt"),("format"="adm"));
 
-write output to nc1:"rttest/groupby-orderby-count.adm";
+write output to nc1:"rttest/misc_groupby-orderby-count.adm";
 
 for $t in dataset('TwitterData')
 let $tokens := word-tokens($t.text)
diff --git a/asterix-app/src/test/resources/runtimets/queries/misc/ifthenelse_01.aql b/asterix-app/src/test/resources/runtimets/queries/misc/ifthenelse_01.aql
new file mode 100644
index 0000000..a65e675
--- /dev/null
+++ b/asterix-app/src/test/resources/runtimets/queries/misc/ifthenelse_01.aql
@@ -0,0 +1,8 @@
+use dataverse test;
+
+write output to nc1:"rttest/misc_ifthenelse_01.adm";
+
+if (2>1) then
+    20
+else
+    10
diff --git a/asterix-app/src/test/resources/runtimets/queries/is-null_01.aql b/asterix-app/src/test/resources/runtimets/queries/misc/is-null_01.aql
similarity index 67%
rename from asterix-app/src/test/resources/runtimets/queries/is-null_01.aql
rename to asterix-app/src/test/resources/runtimets/queries/misc/is-null_01.aql
index ca6ac7a..9005392 100644
--- a/asterix-app/src/test/resources/runtimets/queries/is-null_01.aql
+++ b/asterix-app/src/test/resources/runtimets/queries/misc/is-null_01.aql
@@ -3,6 +3,6 @@
 
 use dataverse test;
 
-write output to nc1:"rttest/is-null_01.adm";
+write output to nc1:"rttest/misc_is-null_01.adm";
 
-[is-null(null), is-null(10)]
\ No newline at end of file
+[is-null(null), is-null(10)]
diff --git a/asterix-app/src/test/resources/runtimets/queries/nested-loop-join_01.aql b/asterix-app/src/test/resources/runtimets/queries/misc/nested-loop-join_01.aql
similarity index 94%
rename from asterix-app/src/test/resources/runtimets/queries/nested-loop-join_01.aql
rename to asterix-app/src/test/resources/runtimets/queries/misc/nested-loop-join_01.aql
index 79b3a9f..f148d2e 100644
--- a/asterix-app/src/test/resources/runtimets/queries/nested-loop-join_01.aql
+++ b/asterix-app/src/test/resources/runtimets/queries/misc/nested-loop-join_01.aql
@@ -28,7 +28,7 @@
 using "edu.uci.ics.asterix.external.dataset.adapter.NCFileSystemAdapter" 
 (("path"="nc1://data/users-visitors-small/visitors.json"),("format"="adm"));
 
-write output to nc1:'rttest/nested-loop-join_01.adm';
+write output to nc1:'rttest/misc_nested-loop-join_01.adm';
 
 for $user in dataset('Users')
 for $visitor in dataset('Visitors')
diff --git a/asterix-app/src/test/resources/runtimets/queries/range_01.aql b/asterix-app/src/test/resources/runtimets/queries/misc/range_01.aql
similarity index 65%
rename from asterix-app/src/test/resources/runtimets/queries/range_01.aql
rename to asterix-app/src/test/resources/runtimets/queries/misc/range_01.aql
index 0f38113..9c5991d 100644
--- a/asterix-app/src/test/resources/runtimets/queries/range_01.aql
+++ b/asterix-app/src/test/resources/runtimets/queries/misc/range_01.aql
@@ -1,7 +1,7 @@
 drop dataverse test if exists;
 create dataverse test;
 
-write output to nc1:"rttest/range_01.adm";
+write output to nc1:"rttest/misc_range_01.adm";
 
 for $x in range(20,30)
 return $x
diff --git a/asterix-app/src/test/resources/runtimets/queries/tid_01.aql b/asterix-app/src/test/resources/runtimets/queries/misc/tid_01.aql
similarity index 67%
rename from asterix-app/src/test/resources/runtimets/queries/tid_01.aql
rename to asterix-app/src/test/resources/runtimets/queries/misc/tid_01.aql
index 3cd8396..ff216ea 100644
--- a/asterix-app/src/test/resources/runtimets/queries/tid_01.aql
+++ b/asterix-app/src/test/resources/runtimets/queries/misc/tid_01.aql
@@ -1,7 +1,7 @@
 drop dataverse test if exists;
 create dataverse test;
 
-write output to nc1:"rttest/tid_01.adm";
+write output to nc1:"rttest/misc_tid_01.adm";
 
 for $x at $i in ["a","b","c"]
 return $i
diff --git a/asterix-app/src/test/resources/runtimets/queries/year_01.aql b/asterix-app/src/test/resources/runtimets/queries/misc/year_01.aql
similarity index 61%
rename from asterix-app/src/test/resources/runtimets/queries/year_01.aql
rename to asterix-app/src/test/resources/runtimets/queries/misc/year_01.aql
index da93e96..f431a52 100644
--- a/asterix-app/src/test/resources/runtimets/queries/year_01.aql
+++ b/asterix-app/src/test/resources/runtimets/queries/misc/year_01.aql
@@ -1,6 +1,6 @@
 drop dataverse test if exists;
 create dataverse test;
 
-write output to nc1:"rttest/year_01.adm";
+write output to nc1:"rttest/misc_year_01.adm";
 
 year("1996-12-01")
diff --git a/asterix-app/src/test/resources/runtimets/results/float_01.adm b/asterix-app/src/test/resources/runtimets/results/misc/float_01.adm
similarity index 100%
rename from asterix-app/src/test/resources/runtimets/results/float_01.adm
rename to asterix-app/src/test/resources/runtimets/results/misc/float_01.adm
diff --git a/asterix-app/src/test/resources/runtimets/results/groupby-orderby-count.adm b/asterix-app/src/test/resources/runtimets/results/misc/groupby-orderby-count.adm
similarity index 100%
rename from asterix-app/src/test/resources/runtimets/results/groupby-orderby-count.adm
rename to asterix-app/src/test/resources/runtimets/results/misc/groupby-orderby-count.adm
diff --git a/asterix-app/src/test/resources/runtimets/results/ifthenelse_01.adm b/asterix-app/src/test/resources/runtimets/results/misc/ifthenelse_01.adm
similarity index 100%
rename from asterix-app/src/test/resources/runtimets/results/ifthenelse_01.adm
rename to asterix-app/src/test/resources/runtimets/results/misc/ifthenelse_01.adm
diff --git a/asterix-app/src/test/resources/runtimets/results/is-null_01.adm b/asterix-app/src/test/resources/runtimets/results/misc/is-null_01.adm
similarity index 100%
rename from asterix-app/src/test/resources/runtimets/results/is-null_01.adm
rename to asterix-app/src/test/resources/runtimets/results/misc/is-null_01.adm
diff --git a/asterix-app/src/test/resources/runtimets/results/nested-loop-join_01.adm b/asterix-app/src/test/resources/runtimets/results/misc/nested-loop-join_01.adm
similarity index 100%
rename from asterix-app/src/test/resources/runtimets/results/nested-loop-join_01.adm
rename to asterix-app/src/test/resources/runtimets/results/misc/nested-loop-join_01.adm
diff --git a/asterix-app/src/test/resources/runtimets/results/range_01.adm b/asterix-app/src/test/resources/runtimets/results/misc/range_01.adm
similarity index 100%
rename from asterix-app/src/test/resources/runtimets/results/range_01.adm
rename to asterix-app/src/test/resources/runtimets/results/misc/range_01.adm
diff --git a/asterix-app/src/test/resources/runtimets/results/tid_01.adm b/asterix-app/src/test/resources/runtimets/results/misc/tid_01.adm
similarity index 100%
rename from asterix-app/src/test/resources/runtimets/results/tid_01.adm
rename to asterix-app/src/test/resources/runtimets/results/misc/tid_01.adm
diff --git a/asterix-app/src/test/resources/runtimets/results/year_01.adm b/asterix-app/src/test/resources/runtimets/results/misc/year_01.adm
similarity index 100%
rename from asterix-app/src/test/resources/runtimets/results/year_01.adm
rename to asterix-app/src/test/resources/runtimets/results/misc/year_01.adm
diff --git a/asterix-test-framework/pom.xml b/asterix-test-framework/pom.xml
new file mode 100755
index 0000000..2f0142e
--- /dev/null
+++ b/asterix-test-framework/pom.xml
@@ -0,0 +1,36 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+	<modelVersion>4.0.0</modelVersion>
+	<parent>
+		<artifactId>asterix</artifactId>
+		<groupId>edu.uci.ics.asterix</groupId>
+		<version>0.0.4-SNAPSHOT</version>
+	</parent>
+	<artifactId>asterix-test-framework</artifactId>
+	<build>
+		<plugins>
+			<plugin>
+				<groupId>org.apache.maven.plugins</groupId>
+				<artifactId>maven-compiler-plugin</artifactId>
+				<version>2.0.2</version>
+				<configuration>
+					<source>1.6</source>
+					<target>1.6</target>
+				</configuration>
+			</plugin>
+            <plugin>
+                <groupId>org.jvnet.jaxb2.maven2</groupId>
+                <artifactId>maven-jaxb2-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <goals>
+                            <goal>generate</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+		</plugins>
+	</build>
+
+	<dependencies>
+	</dependencies>
+</project>
diff --git a/asterix-test-framework/src/main/java/edu/uci/ics/asterix/testframework/context/TestCaseContext.java b/asterix-test-framework/src/main/java/edu/uci/ics/asterix/testframework/context/TestCaseContext.java
new file mode 100644
index 0000000..d5164db
--- /dev/null
+++ b/asterix-test-framework/src/main/java/edu/uci/ics/asterix/testframework/context/TestCaseContext.java
@@ -0,0 +1,103 @@
+package edu.uci.ics.asterix.testframework.context;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+
+import edu.uci.ics.asterix.testframework.xml.TestCase;
+import edu.uci.ics.asterix.testframework.xml.TestCase.CompilationUnit;
+import edu.uci.ics.asterix.testframework.xml.TestGroup;
+import edu.uci.ics.asterix.testframework.xml.TestSuite;
+import edu.uci.ics.asterix.testframework.xml.TestSuiteParser;
+
+public class TestCaseContext {
+    public static final String DEFAULT_TESTSUITE_XML_NAME = "testsuite.xml";
+
+    private File tsRoot;
+
+    private TestSuite testSuite;
+
+    private TestGroup[] testGroups;
+
+    private TestCase testCase;
+
+    public TestCaseContext(File tsRoot, TestSuite testSuite, TestGroup[] testGroups, TestCase testCase) {
+        this.tsRoot = tsRoot;
+        this.testSuite = testSuite;
+        this.testGroups = testGroups;
+        this.testCase = testCase;
+    }
+
+    public File getTsRoot() {
+        return tsRoot;
+    }
+
+    public TestSuite getTestSuite() {
+        return testSuite;
+    }
+
+    public TestGroup[] getTestGroups() {
+        return testGroups;
+    }
+
+    public TestCase getTestCase() {
+        return testCase;
+    }
+
+    public File getTestFile(CompilationUnit cUnit) {
+        File path = tsRoot;
+        path = new File(path, testSuite.getQueryOffsetPath());
+        path = new File(path, testCase.getFilePath());
+        return new File(path, cUnit.getName() + testSuite.getQueryFileExtension());
+    }
+
+    public File getExpectedResultFile(CompilationUnit cUnit) {
+        File path = tsRoot;
+        path = new File(path, testSuite.getResultOffsetPath());
+        path = new File(path, testCase.getFilePath());
+        return new File(path, cUnit.getOutputFile().getValue());
+    }
+    
+    public File getActualResultFile(CompilationUnit cUnit, File actualResultsBase) {
+        File path = actualResultsBase;
+        path = new File(path, testSuite.getResultOffsetPath());
+        path = new File(path, testCase.getFilePath());
+        return new File(path, cUnit.getOutputFile().getValue());
+    }
+
+    public static class Builder {
+        public Builder() {
+        }
+
+        public List<TestCaseContext> build(File tsRoot) throws Exception {
+            return build(tsRoot, DEFAULT_TESTSUITE_XML_NAME);
+        }
+
+        public List<TestCaseContext> build(File tsRoot, String tsXMLFilePath) throws Exception {
+            File tsFile = new File(tsRoot, tsXMLFilePath);
+            TestSuiteParser tsp = new TestSuiteParser();
+            TestSuite ts = tsp.parse(tsFile);
+            List<TestCaseContext> tccs = new ArrayList<TestCaseContext>();
+            List<TestGroup> tgPath = new ArrayList<TestGroup>();
+            addContexts(tsRoot, ts, tgPath, ts.getTestGroup(), tccs);
+            return tccs;
+        }
+
+        private void addContexts(File tsRoot, TestSuite ts, List<TestGroup> tgPath, List<TestGroup> testGroups,
+                List<TestCaseContext> tccs) {
+            for (TestGroup tg : testGroups) {
+                tgPath.add(tg);
+                addContexts(tsRoot, ts, tgPath, tccs);
+                tgPath.remove(tgPath.size() - 1);
+            }
+        }
+
+        private void addContexts(File tsRoot, TestSuite ts, List<TestGroup> tgPath, List<TestCaseContext> tccs) {
+            TestGroup tg = tgPath.get(tgPath.size() - 1);
+            for (TestCase tc : tg.getTestCase()) {
+                tccs.add(new TestCaseContext(tsRoot, ts, tgPath.toArray(new TestGroup[tgPath.size()]), tc));
+            }
+            addContexts(tsRoot, ts, tgPath, tg.getTestGroup(), tccs);
+        }
+    }
+}
\ No newline at end of file
diff --git a/asterix-test-framework/src/main/java/edu/uci/ics/asterix/testframework/xml/TestSuiteParser.java b/asterix-test-framework/src/main/java/edu/uci/ics/asterix/testframework/xml/TestSuiteParser.java
new file mode 100644
index 0000000..0c544ac
--- /dev/null
+++ b/asterix-test-framework/src/main/java/edu/uci/ics/asterix/testframework/xml/TestSuiteParser.java
@@ -0,0 +1,15 @@
+package edu.uci.ics.asterix.testframework.xml;
+
+import java.io.File;
+
+import javax.xml.bind.JAXBContext;
+
+public class TestSuiteParser {
+    public TestSuiteParser() {
+    }
+
+    public TestSuite parse(File testSuiteCatalog) throws Exception {
+        JAXBContext ctx = JAXBContext.newInstance(TestSuite.class);
+        return (TestSuite) ctx.createUnmarshaller().unmarshal(testSuiteCatalog);
+    }
+}
\ No newline at end of file
diff --git a/asterix-test-framework/src/main/resources/Catalog.xsd b/asterix-test-framework/src/main/resources/Catalog.xsd
new file mode 100755
index 0000000..feadbd7
--- /dev/null
+++ b/asterix-test-framework/src/main/resources/Catalog.xsd
@@ -0,0 +1,210 @@
+<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"

+   xmlns:test="urn:xml.testframework.asterix.ics.uci.edu"

+   targetNamespace="urn:xml.testframework.asterix.ics.uci.edu" elementFormDefault="qualified">

+

+   <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  -->

+   <!-- test-suite - top level element                                       -->

+   <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  -->

+

+   <xs:element name="test-suite">

+      <xs:annotation>

+         <xs:documentation>

+            This is the top level element for documents that use this schema.

+         </xs:documentation>

+      </xs:annotation>

+

+      <xs:complexType>

+         <xs:sequence>

+            <xs:element ref="test:test-group" maxOccurs="unbounded"/>

+         </xs:sequence>

+

+         <xs:attribute name="CatalogDesignDate" type="xs:date" use="required"/>

+

+         <xs:attribute name="ResultOffsetPath" type="test:SimplifiedRelativeFilePath" use="required">

+            <xs:annotation>

+               <xs:documentation>

+                  offset from root to results

+               </xs:documentation>

+            </xs:annotation>

+         </xs:attribute>

+

+         <xs:attribute name="QueryOffsetPath" type="test:SimplifiedRelativeFilePath"

+            use="required">

+            <xs:annotation>

+               <xs:documentation>

+                  offset from root to Query expression files

+               </xs:documentation>

+            </xs:annotation>

+         </xs:attribute>

+

+         <!-- file extension for XQuery expression files -->

+         <xs:attribute name="QueryFileExtension" type="xs:string" use="required">

+            <xs:annotation>

+               <xs:documentation>

+                  file extension for Query files

+               </xs:documentation>

+            </xs:annotation>

+         </xs:attribute>

+

+      </xs:complexType>

+

+      <xs:unique name="unique-test-group">

+         <xs:selector xpath=".//test:test-group"/>

+         <xs:field xpath="@name"/>

+      </xs:unique>

+

+   </xs:element>

+

+

+   <!-- SimplifiedRelativeFilePath type                                      -->

+

+   <xs:simpleType name="SimplifiedRelativeFilePath">

+      <xs:restriction base="xs:anyURI">

+         <xs:pattern value="([a-zA-Z0-9\-\.]+/)+"/>

+      </xs:restriction>

+   </xs:simpleType>

+

+   <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  -->

+   <!-- test-group                                                           -->

+   <!--                                                                      -->

+   <!-- Group of test cases and test groups.                                 -->

+   <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  -->

+

+   <xs:element name="test-group">

+      <xs:annotation>

+         <xs:documentation>

+            Group of test cases and test groups.

+         </xs:documentation>

+      </xs:annotation>

+

+      <xs:complexType>

+         <xs:sequence>

+            <xs:element name="test-case" type="test:test-case" minOccurs="0" maxOccurs="unbounded">

+               <xs:unique name="unique-expected-error">

+                  <xs:selector xpath=".//test:expected-error"/>

+                  <xs:field xpath="."/>

+               </xs:unique>

+            </xs:element>

+

+            <xs:element ref="test:test-group" minOccurs="0" maxOccurs="unbounded"/>

+         </xs:sequence>

+         <xs:attribute name="name" type="xs:string" use="required"/>

+      </xs:complexType>

+   </xs:element>

+

+

+   <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  -->

+   <!-- test-case                                                            -->

+   <!--                                                                      -->

+   <!-- A test case to be run.                                               -->

+   <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  -->

+

+   <xs:complexType name="test-case">

+      <xs:sequence>

+         <xs:element name="description" type="test:description"/>

+

+         <xs:element name="compilation-unit" minOccurs="1" maxOccurs="unbounded">

+            <xs:complexType>

+               <xs:sequence>

+                  <xs:element name="description" type="test:description" minOccurs="0"/>

+                  <xs:element name="output-file" minOccurs="0">

+                     <xs:annotation>

+                        <xs:documentation>

+                           Zero or one file containing expected results for this query.

+                        </xs:documentation>

+                     </xs:annotation>

+                     <xs:complexType>

+                        <xs:simpleContent>

+                           <xs:extension base="xs:string">

+                              <xs:attribute name="compare" type="test:comparison-enum" use="required"/>

+                           </xs:extension>

+                        </xs:simpleContent>

+                     </xs:complexType>

+                  </xs:element>

+

+

+                  <!-- Zero or more expected errors for this query -->

+

+                  <xs:element name="expected-error" minOccurs="0" maxOccurs="unbounded">

+                     <xs:annotation>

+                        <xs:documentation>

+                           Zero or more expected errors for this query.

+                        </xs:documentation>

+                     </xs:annotation>

+

+                     <xs:complexType>

+                        <xs:simpleContent>

+                           <xs:extension base="test:ErrorCode">

+                           </xs:extension>

+                        </xs:simpleContent>

+                     </xs:complexType>

+                  </xs:element>

+

+               </xs:sequence>

+

+               <!-- This name is always equal to the name of the test case -->

+               <xs:attribute name="name" type="xs:string" use="required"/>

+            </xs:complexType>

+         </xs:element>

+

+         <!-- Zero or more files containing expected results for this query -->

+

+      </xs:sequence>

+

+      <!-- The filename for this query can be constructed from:              -->

+      <!--    the QueryOffsetPath                                            -->

+      <!--    the FilePath                                                   -->

+      <!--    the name                                                       -->

+      <!--    the QueryFileExtension                                         -->

+

+      <xs:attribute name="FilePath" type="test:SimplifiedRelativeFilePath" use="required"/>

+      <xs:attribute name="date" type="xs:date" use="required"/>

+   </xs:complexType>

+

+   <!-- comparison-enum type                                                 -->

+   <!--    Identify the type of comparison used to determine whether an      -->

+   <!--    expected result and an actual result match.                       -->

+

+   <xs:simpleType name="comparison-enum">

+      <xs:annotation>

+         <xs:documentation>

+            Identify the type of comparison used to determine whether an

+            expected result and an actual result match.

+         </xs:documentation>

+      </xs:annotation>

+

+      <xs:restriction base="xs:string">

+         <xs:enumeration value="XML"/>

+         <xs:enumeration value="Text"/>

+         <xs:enumeration value="Inspect"/>

+         <xs:enumeration value="Ignore"/>

+      </xs:restriction>

+   </xs:simpleType>

+

+   <!-- description type                                                     -->

+

+   <xs:complexType name="description">

+      <xs:simpleContent>

+         <xs:extension base="xs:string">

+            <xs:attribute name="last-mod" type="xs:date"/>

+         </xs:extension>

+      </xs:simpleContent>

+   </xs:complexType>

+

+

+   <!-- ErrorCode type                                                       -->

+   <!--   * is used to mean that any error code is acceptable                -->

+

+   <xs:simpleType name="ErrorCode">

+      <xs:annotation>

+         <xs:documentation>

+            * is used to mean that any error code is acceptable

+         </xs:documentation>

+      </xs:annotation>

+

+      <xs:restriction base="xs:string">

+         <xs:pattern value="\*|([A-Z]{4}[0-9]{4})"/>

+      </xs:restriction>

+   </xs:simpleType>

+   

+</xs:schema>

diff --git a/pom.xml b/pom.xml
index 95f8103..506d57d 100644
--- a/pom.xml
+++ b/pom.xml
@@ -78,6 +78,7 @@
                 <module>asterix-external-data</module>
                 <module>asterix-metadata</module>
                 <module>asterix-dist</module>
+                <module>asterix-test-framework</module>
         </modules>
 
 	<repositories>