Issue 867: Test cases for CSV with headers and various line separators.

Change-Id: I046aa30824d14cbc894a28719c6c6560f46d0133
Reviewed-on: https://asterix-gerrit.ics.uci.edu/247
Reviewed-by: Ian Maxon <imaxon@uci.edu>
Tested-by: Jenkins <jenkins@fulliautomatix.ics.uci.edu>
Reviewed-by: Preston Carman <ecarm002@ucr.edu>
diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..803e30c
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,17 @@
+# Set the default behavior, in case people don't have core.autocrlf set.
+* text=auto
+
+# Explicitly declare text files you want to always be normalized and converted
+# to native line endings on checkout.
+*.java text
+*.xml text
+*.xsd text
+
+# Declare files that will always have CRLF line endings on checkout.
+*.crlf text eol=crlf
+
+# Declare files that will always have CR line endings on checkout.
+*.cr text eol=cr
+
+# Declare files that will always have LF line endings on checkout.
+*.lf test eol=lf
diff --git a/asterix-app/data/csv/sample_08_header.csv.cr b/asterix-app/data/csv/sample_08_header.csv.cr
new file mode 100644
index 0000000..a539b24
--- /dev/null
+++ b/asterix-app/data/csv/sample_08_header.csv.cr
@@ -0,0 +1 @@
+id,float,"double","date",time,datetime
1,0.899682764,5.6256,2013-08-07,07:22:35,1979-02-25T23:48:27.034
2,0.669052398,,-1923-03-29,19:33:34,-1979-02-25T23:48:27.002
3,0.572733058,192674,-1923-03-28,19:33:34,-1979-02-25T23:48:27.001
4,,192674,-1923-03-27,19:33:34,-1979-02-25T23:48:27.001
5,0.572733058,192674,,19:33:34,-1979-02-25T23:48:27.001
6,0.572733058,192674,-1923-03-25,,-1979-02-25T23:48:27.001
7,0.572733058,192674,-1923-03-24,19:33:34,
8,,,,,

\ No newline at end of file
diff --git a/asterix-app/data/csv/sample_08_header.csv b/asterix-app/data/csv/sample_08_header.csv.crlf
similarity index 99%
rename from asterix-app/data/csv/sample_08_header.csv
rename to asterix-app/data/csv/sample_08_header.csv.crlf
index 7444e77..168340b 100644
--- a/asterix-app/data/csv/sample_08_header.csv
+++ b/asterix-app/data/csv/sample_08_header.csv.crlf
@@ -6,4 +6,4 @@
 5,0.572733058,192674,,19:33:34,-1979-02-25T23:48:27.001
 6,0.572733058,192674,-1923-03-25,,-1979-02-25T23:48:27.001
 7,0.572733058,192674,-1923-03-24,19:33:34,
-8,,,,,
\ No newline at end of file
+8,,,,,
diff --git a/asterix-app/data/csv/sample_08_header.csv b/asterix-app/data/csv/sample_08_header.csv.lf
similarity index 99%
copy from asterix-app/data/csv/sample_08_header.csv
copy to asterix-app/data/csv/sample_08_header.csv.lf
index 7444e77..168340b 100644
--- a/asterix-app/data/csv/sample_08_header.csv
+++ b/asterix-app/data/csv/sample_08_header.csv.lf
@@ -6,4 +6,4 @@
 5,0.572733058,192674,,19:33:34,-1979-02-25T23:48:27.001
 6,0.572733058,192674,-1923-03-25,,-1979-02-25T23:48:27.001
 7,0.572733058,192674,-1923-03-24,19:33:34,
-8,,,,,
\ No newline at end of file
+8,,,,,
diff --git a/asterix-app/src/test/resources/runtimets/queries/load/csv_08/csv_08.1.ddl.aql b/asterix-app/src/test/resources/runtimets/queries/load/csv_08_header_cr/csv_08.1.ddl.aql
similarity index 100%
rename from asterix-app/src/test/resources/runtimets/queries/load/csv_08/csv_08.1.ddl.aql
rename to asterix-app/src/test/resources/runtimets/queries/load/csv_08_header_cr/csv_08.1.ddl.aql
diff --git a/asterix-app/src/test/resources/runtimets/queries/load/csv_08/csv_08.2.update.aql b/asterix-app/src/test/resources/runtimets/queries/load/csv_08_header_cr/csv_08.2.update.aql
similarity index 65%
rename from asterix-app/src/test/resources/runtimets/queries/load/csv_08/csv_08.2.update.aql
rename to asterix-app/src/test/resources/runtimets/queries/load/csv_08_header_cr/csv_08.2.update.aql
index e8eecf1..f6a2017 100644
--- a/asterix-app/src/test/resources/runtimets/queries/load/csv_08/csv_08.2.update.aql
+++ b/asterix-app/src/test/resources/runtimets/queries/load/csv_08_header_cr/csv_08.2.update.aql
@@ -9,4 +9,4 @@
 
 load dataset testds
 using "edu.uci.ics.asterix.external.dataset.adapter.NCFileSystemAdapter"
-(("path"="nc1://data/csv/sample_08_header.csv"),("format"="delimited-text"),("header"="true"));
\ No newline at end of file
+(("path"="nc1://data/csv/sample_08_header.csv.cr"),("format"="delimited-text"),("header"="true"));
\ No newline at end of file
diff --git a/asterix-app/src/test/resources/runtimets/queries/load/csv_08/csv_08.3.query.aql b/asterix-app/src/test/resources/runtimets/queries/load/csv_08_header_cr/csv_08.3.query.aql
similarity index 100%
rename from asterix-app/src/test/resources/runtimets/queries/load/csv_08/csv_08.3.query.aql
rename to asterix-app/src/test/resources/runtimets/queries/load/csv_08_header_cr/csv_08.3.query.aql
diff --git a/asterix-app/src/test/resources/runtimets/queries/load/csv_08/csv_08.1.ddl.aql b/asterix-app/src/test/resources/runtimets/queries/load/csv_08_header_crlf/csv_08.1.ddl.aql
similarity index 100%
copy from asterix-app/src/test/resources/runtimets/queries/load/csv_08/csv_08.1.ddl.aql
copy to asterix-app/src/test/resources/runtimets/queries/load/csv_08_header_crlf/csv_08.1.ddl.aql
diff --git a/asterix-app/src/test/resources/runtimets/queries/load/csv_08/csv_08.2.update.aql b/asterix-app/src/test/resources/runtimets/queries/load/csv_08_header_crlf/csv_08.2.update.aql
similarity index 64%
copy from asterix-app/src/test/resources/runtimets/queries/load/csv_08/csv_08.2.update.aql
copy to asterix-app/src/test/resources/runtimets/queries/load/csv_08_header_crlf/csv_08.2.update.aql
index e8eecf1..7634ebb 100644
--- a/asterix-app/src/test/resources/runtimets/queries/load/csv_08/csv_08.2.update.aql
+++ b/asterix-app/src/test/resources/runtimets/queries/load/csv_08_header_crlf/csv_08.2.update.aql
@@ -9,4 +9,4 @@
 
 load dataset testds
 using "edu.uci.ics.asterix.external.dataset.adapter.NCFileSystemAdapter"
-(("path"="nc1://data/csv/sample_08_header.csv"),("format"="delimited-text"),("header"="true"));
\ No newline at end of file
+(("path"="nc1://data/csv/sample_08_header.csv.crlf"),("format"="delimited-text"),("header"="true"));
\ No newline at end of file
diff --git a/asterix-app/src/test/resources/runtimets/queries/load/csv_08/csv_08.3.query.aql b/asterix-app/src/test/resources/runtimets/queries/load/csv_08_header_crlf/csv_08.3.query.aql
similarity index 100%
copy from asterix-app/src/test/resources/runtimets/queries/load/csv_08/csv_08.3.query.aql
copy to asterix-app/src/test/resources/runtimets/queries/load/csv_08_header_crlf/csv_08.3.query.aql
diff --git a/asterix-app/src/test/resources/runtimets/queries/load/csv_08/csv_08.1.ddl.aql b/asterix-app/src/test/resources/runtimets/queries/load/csv_08_header_lf/csv_08.1.ddl.aql
similarity index 100%
copy from asterix-app/src/test/resources/runtimets/queries/load/csv_08/csv_08.1.ddl.aql
copy to asterix-app/src/test/resources/runtimets/queries/load/csv_08_header_lf/csv_08.1.ddl.aql
diff --git a/asterix-app/src/test/resources/runtimets/queries/load/csv_08/csv_08.2.update.aql b/asterix-app/src/test/resources/runtimets/queries/load/csv_08_header_lf/csv_08.2.update.aql
similarity index 65%
copy from asterix-app/src/test/resources/runtimets/queries/load/csv_08/csv_08.2.update.aql
copy to asterix-app/src/test/resources/runtimets/queries/load/csv_08_header_lf/csv_08.2.update.aql
index e8eecf1..4a7c203 100644
--- a/asterix-app/src/test/resources/runtimets/queries/load/csv_08/csv_08.2.update.aql
+++ b/asterix-app/src/test/resources/runtimets/queries/load/csv_08_header_lf/csv_08.2.update.aql
@@ -9,4 +9,4 @@
 
 load dataset testds
 using "edu.uci.ics.asterix.external.dataset.adapter.NCFileSystemAdapter"
-(("path"="nc1://data/csv/sample_08_header.csv"),("format"="delimited-text"),("header"="true"));
\ No newline at end of file
+(("path"="nc1://data/csv/sample_08_header.csv.lf"),("format"="delimited-text"),("header"="true"));
\ No newline at end of file
diff --git a/asterix-app/src/test/resources/runtimets/queries/load/csv_08/csv_08.3.query.aql b/asterix-app/src/test/resources/runtimets/queries/load/csv_08_header_lf/csv_08.3.query.aql
similarity index 100%
copy from asterix-app/src/test/resources/runtimets/queries/load/csv_08/csv_08.3.query.aql
copy to asterix-app/src/test/resources/runtimets/queries/load/csv_08_header_lf/csv_08.3.query.aql
diff --git a/asterix-app/src/test/resources/runtimets/testsuite.xml b/asterix-app/src/test/resources/runtimets/testsuite.xml
index b571514..9df6301 100644
--- a/asterix-app/src/test/resources/runtimets/testsuite.xml
+++ b/asterix-app/src/test/resources/runtimets/testsuite.xml
@@ -5943,7 +5943,17 @@
             </compilation-unit>
         </test-case>
         <test-case FilePath="load">
-            <compilation-unit name="csv_08">
+            <compilation-unit name="csv_08_header_cr">
+                <output-dir compare="Text">csv_08</output-dir>
+            </compilation-unit>
+        </test-case>
+        <test-case FilePath="load">
+            <compilation-unit name="csv_08_header_lf">
+                <output-dir compare="Text">csv_08</output-dir>
+            </compilation-unit>
+        </test-case>
+        <test-case FilePath="load">
+            <compilation-unit name="csv_08_header_crlf">
                 <output-dir compare="Text">csv_08</output-dir>
             </compilation-unit>
         </test-case>
diff --git a/asterix-runtime/src/main/java/edu/uci/ics/asterix/runtime/operators/file/DelimitedDataParser.java b/asterix-runtime/src/main/java/edu/uci/ics/asterix/runtime/operators/file/DelimitedDataParser.java
index 0dd9014..fc38d37 100644
--- a/asterix-runtime/src/main/java/edu/uci/ics/asterix/runtime/operators/file/DelimitedDataParser.java
+++ b/asterix-runtime/src/main/java/edu/uci/ics/asterix/runtime/operators/file/DelimitedDataParser.java
@@ -51,7 +51,6 @@
     private int[] fldIds;
     private ArrayBackedValueStorage[] nameBuffers;
     private boolean areAllNullFields;
-    private int fieldCount;
 
     public DelimitedDataParser(ARecordType recordType, IValueParserFactory[] valueParserFactories, char fieldDelimter,
             char quote, boolean hasHeader) {
@@ -105,19 +104,18 @@
 
     @Override
     public boolean parse(DataOutput out) throws AsterixException, IOException {
-        if (hasHeader && cursor.lineCount == 1) {
+        if (hasHeader && cursor.recordCount == 0) {
             // Consume all fields of first record
             cursor.nextRecord();
-            while (cursor.nextField(fieldCount));
+            while (cursor.nextField());
         }
         while (cursor.nextRecord()) {
             recBuilder.reset(recordType);
             recBuilder.init();
             areAllNullFields = true;
             
-            fieldCount = 0;
             for (int i = 0; i < valueParsers.length; ++i) {
-                if (!cursor.nextField(fieldCount)) {
+                if (!cursor.nextField()) {
                     break;
                 }
                 fieldValueBuffer.reset();
@@ -129,7 +127,7 @@
                     // empty string
                     if (recordType.getFieldTypes()[i].getTypeTag() != ATypeTag.UNION
                             || !NonTaggedFormatUtil.isOptionalField((AUnionType) recordType.getFieldTypes()[i])) {
-                        throw new AsterixException("At line: " + cursor.lineCount + " - Field " + i
+                        throw new AsterixException("At record: " + cursor.recordCount + " - Field " + cursor.fieldCount
                                 + " is not an optional type so it cannot accept null value. ");
                     }
                     fieldValueBufferOutput.writeByte(ATypeTag.NULL.serialize());
@@ -151,7 +149,6 @@
                 } else {
                     recBuilder.addField(fldIds[i], fieldValueBuffer);
                 }
-                fieldCount++;
             }
 
             if (!areAllNullFields) {
diff --git a/asterix-test-framework/src/main/resources/Catalog.xsd b/asterix-test-framework/src/main/resources/Catalog.xsd
index e43acdc..65ba7a4 100644
--- a/asterix-test-framework/src/main/resources/Catalog.xsd
+++ b/asterix-test-framework/src/main/resources/Catalog.xsd
@@ -1,221 +1,221 @@
-<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>

-

-      </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-dir" 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:attribute name="category" type="test:category-enum"/>

-   </xs:complexType>

-

-   <!-- category-enum type                                                   -->

-   <!--    Identify which category of test this is. Currently only "slow".   -->

-

-   <xs:simpleType name="category-enum">

-      <xs:annotation>

-         <xs:documentation>

-            Identify the category of test, for limiting when it is run.

-         </xs:documentation>

-      </xs:annotation>

-

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

-         <xs:enumeration value="slow"/>

-      </xs:restriction>

-   </xs:simpleType>

-

-   <!-- 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:enumeration value="JSON"/>

-         <xs:enumeration value="CSV"/>

-         <xs:enumeration value="CSV_Header"/>

-      </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>

+<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>
+
+      </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-dir" 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:attribute name="category" type="test:category-enum"/>
+   </xs:complexType>
+
+   <!-- category-enum type                                                   -->
+   <!--    Identify which category of test this is. Currently only "slow".   -->
+
+   <xs:simpleType name="category-enum">
+      <xs:annotation>
+         <xs:documentation>
+            Identify the category of test, for limiting when it is run.
+         </xs:documentation>
+      </xs:annotation>
+
+      <xs:restriction base="xs:string">
+         <xs:enumeration value="slow"/>
+      </xs:restriction>
+   </xs:simpleType>
+
+   <!-- 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:enumeration value="JSON"/>
+         <xs:enumeration value="CSV"/>
+         <xs:enumeration value="CSV_Header"/>
+      </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>