LucidDbSqlDiffTesting

From LucidDB Wiki
Jump to: navigation, search

Contents

Introduction

This page describes how to make use of some of the diff-based testing frameworks built into LucidDB for the purpose of test-driven ETL script development, as raised in this thread.

The necessary glue code is provided inline, as well as some simple example test scripts.

Setup

Create a directory for your tests outside of your LucidDB installation directory. We'll refer to the test directory as test, and the LucidDB installation directory as luciddb (see LucidDbGettingStarted for installation instructions).

Create Test Class

Copy and paste the Java code below into a file named test/SqlDiffTest.java:

import java.io.*;
import junit.extensions.*;
import junit.framework.*;
import net.sf.farrago.util.*;
import net.sf.farrago.test.*;

/**
 * SqlDiffTest runs a .sql script via sqlline and diffs
 * the output against a golden .ref file.
 */
public class SqlDiffTest extends FarragoTestCase
{

    public SqlDiffTest(String testName)
        throws Exception
    {
        super(testName);
    }

    public static Test suite()
        throws Exception
    {
        return gatherSuite(
            FarragoProperties.instance().testFilesetUnitsql.get(true),
            new SqlDiffTestFactory() {
                public FarragoTestCase createSqlTest(String testName)
                    throws Exception
                {
                    return new SqlDiffTest(testName);
                }
            });
    }

    protected static Test gatherSuite(
        String fileSet,
        SqlDiffTestFactory fac)
        throws Exception
    {
        StringReader stringReader = new StringReader(fileSet);
        LineNumberReader lineReader = new LineNumberReader(stringReader);
        TestSuite suite = new TestSuite();
        for (;;) {
            String file = lineReader.readLine();
            if (file == null) {
                break;
            }
            suite.addTest(fac.createSqlTest(file));
        }
        return wrappedSuite(suite);
    }
    
    // override FarragoTestCase
    public static Test wrappedSuite(TestSuite suite)
    {
        TestSetup wrapper =
            new TestSetup(suite) {
                protected void setUp()
                    throws Exception
                {
                    staticSetUp();
                }

                protected void tearDown()
                    throws Exception
                {
                    staticTearDown();
                }
            };
        return wrapper;
    }
    
    // override FarragoTestCase
    public static void staticSetUp()
        throws Exception
    {
        CleanupFactory.setFactory(
            new SqlDiffCleanupFactory());
        FarragoTestCase.staticSetUp();
    }
    
    // override FarragoTestCase
    public static void staticTearDown()
        throws Exception
    {
        FarragoTestCase.staticTearDown();
        CleanupFactory.resetFactory();
    }

    protected void runTest()
        throws Exception
    {
        // mask out source control Id, etc
        setRefFileDiffMasks();
        runSqlLineTest(getName());
    }

    public interface SqlDiffTestFactory
    {
        public FarragoTestCase createSqlTest(String testName)
            throws Exception;
    }
    
    /**
     * Custom implementation of CleanupFactory.
     */
    private static class SqlDiffCleanupFactory extends CleanupFactory
    {
        // override CleanupFactory
        public Cleanup newCleanup(String name) throws Exception
        {
            return new SqlDiffCleanup(name);
        }
    }
    
    /**
     * Custom implementation of Cleanup.
     */
    public static class SqlDiffCleanup extends Cleanup
    {
        public SqlDiffCleanup(String name)
            throws Exception
        {
            super(name);
        }

        public void execute()
            throws Exception
        {
        }
    }
}

(The base class FarragoTestCase normally tries to clean up by dropping all non-system objects at the start of each test run, but we don't want that behavior, so we override the Cleanup.execute method to stub it out.)

Create Ant Buildfile

Next, copy and paste the ant build script below into test/build.xml:

<?xml version="1.0" encoding="UTF-8"?>
<project name="test" basedir="." default="test">

  <property name="luciddb.dir" location="/path/to/luciddb" />
  <filelist id="test.sql" dir="." files="test1.sql,test2.sql"/>
  <pathconvert property="test.sql" refid="test.sql"
    pathsep="${line.separator}" dirsep="${file.separator}" />

  <path id="test.classpath">
    <pathelement path="."/>
    <pathelement path="${luciddb.dir}/lib/mdrlibs/jmi.jar"/>
    <pathelement path="${luciddb.dir}/lib/farrago.jar"/>
    <pathelement path="${luciddb.dir}/lib/sqlline.jar"/>
    <pathelement path="${luciddb.dir}/plugin/LucidDbClient.jar"/>
  </path>
  <property name="test.classpath" refid="test.classpath"/>

  <target name="compile">
    <javac
      srcdir="."
      destdir="."
      classpathref="test.classpath">
      <include name="**/*.java"/>
    </javac>
  </target>

  <target name="test">
    <junit printsummary="true">
      <sysproperty key="net.sf.farrago.fileset.unitsql" value="${test.sql}"/>
      <sysproperty key="net.sf.farrago.test.jdbcDriverClass"
        value="com.lucidera.jdbc.LucidDbRmiDriver"/>
      <classpath>
        <pathelement path="${test.classpath}"/>
      </classpath>
      <formatter type="plain"/>
      <test name="SqlDiffTest" todir="."/>
    </junit>
  </target>

</project>

Edit the definition of the luciddb.dir property to point to your LucidDB installation directory.

Note that we use a filelist rather than a fileset for the list of .sql files to be run so that they will be executed in the specified order.

Build Test Class

Now, you'll need a copy of ant installed in order to compile the glue code.

$ ant compile
Buildfile: build.xml

compile:
    [javac] Compiling 1 source file to /home/cleanroom/test

BUILD SUCCESSFUL

Create Test Scripts

Copy and paste this to test/test1.sql:

call applib.drop_schema_if_exists('TEST1', 'CASCADE');

create schema test1;
create table test1.days2007(
date_key date not null primary key,
day_of_week varchar(20));

And test/test2.sql:

insert into test1.days2007
select time_key, day_of_week
from table(applib.fiscal_time_dimension(2007, 1, 1, 2007, 12, 31, 1));

select count(*) from test1.days2007;

Generate Golden Files

Now, make sure the LucidDB server is up, then run the test. The first time, it will fail, since no reference files exist yet:

$ ant test
Buildfile: build.xml

test:
    [junit] Running SqlDiffTest
    [junit] Tests run: 2, Failures: 2, Errors: 0, Time elapsed: 1.779 sec
    [junit] Test SqlDiffTest FAILED

BUILD SUCCESSFUL

This fact shows up in the test report in test/TEST-SqlDiffTest.txt:

...
Testcase: /home/cleanroom/test/test1.sql took 0.446 sec
        FAILED
Reference file /home/cleanroom/test/test1.ref does not exist
...
Testcase: /home/cleanroom/test/test2.sql took 1.137 sec
        FAILED
Reference file /home/cleanroom/test/test2.ref does not exist

Verify the output generated in test/test1.log:

0: jdbc:luciddb:rmi://> call applib.drop_schema_if_exists('TEST1', 'CASCADE');
0: jdbc:luciddb:rmi://> 
0: jdbc:luciddb:rmi://> create schema test1;
0: jdbc:luciddb:rmi://> create table test1.days2007(
> date_key date not null primary key,
> day_of_week varchar(20));
0: jdbc:luciddb:rmi://> 
0: jdbc:luciddb:rmi://> !quit

And test/test2.log:

0: jdbc:luciddb:rmi://> insert into test1.days2007
> select time_key, day_of_week
> from table(applib.fiscal_time_dimension(2007, 1, 1, 2007, 12, 31, 1));
0: jdbc:luciddb:rmi://> 
0: jdbc:luciddb:rmi://> select count(*) from test1.days2007;
+---------+
| EXPR$0  |
+---------+
| 365     |
+---------+
0: jdbc:luciddb:rmi://> 
0: jdbc:luciddb:rmi://> !quit

Now rename them to create the golden files:

$ mv test1.log test1.ref
$ mv test2.log test2.ref

Rerun

Now that the reference files exist, rerunning the test should succeed:

$ ant test
Buildfile: build.xml

test:
    [junit] Running SqlDiffTest
    [junit] Tests run: 2, Failures: 0, Errors: 0, Time elapsed: 1.573 sec
BUILD SUCCESSFUL

TBD

  • Eclipse setup
  • setting haltonfailure=true (normally this is what you want, but for the first run with multiple .sql files, it takes multiple runs to generate the .ref files)
  • various sqlline options and other tricks
Personal tools
Product Documentation