Java tutorial
/* // Licensed to Julian Hyde under one or more contributor license // agreements. See the NOTICE file distributed with this work for // additional information regarding copyright ownership. // // Julian Hyde licenses this file to you under the Apache License, // Version 2.0 (the "License"); you may not use this file except in // compliance with the License. You may obtain a copy of the License at: // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. */ package org.olap4j.test; import org.olap4j.CellSet; import org.olap4j.OlapWrapper; import org.olap4j.impl.Olap4jUtil; import org.olap4j.layout.TraditionalCellSetFormatter; import org.olap4j.mdx.*; import junit.framework.*; import org.apache.commons.dbcp.*; import java.io.*; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.sql.*; import java.util.List; import java.util.Properties; import java.util.regex.Pattern; /** * Context for olap4j tests. * * <p>Also provides static utility methods such as {@link #fold(String)}. * * <p>Properties used by the test framework are described in * {@link org.olap4j.test.TestContext.Property}. * * @author jhyde * @since Jun 7, 2007 */ public class TestContext { public static final String NL = System.getProperty("line.separator"); private static final String indent = " "; private static final String lineBreak2 = "\\\\n\"" + NL + indent + "+ \""; private static final String lineBreak3 = "\\n\"" + NL + indent + "+ \""; private static final Pattern LineBreakPattern = Pattern.compile("\r\n|\r|\n"); private static final Pattern TabPattern = Pattern.compile("\t"); private static Properties testProperties; private static final ThreadLocal<TestContext> THREAD_INSTANCE = new ThreadLocal<TestContext>() { protected TestContext initialValue() { return new TestContext(); } }; /** * The following classes are part of the TCK. Each driver should call them. */ public static final Class<?>[] TCK_CLASSES = { org.olap4j.ConnectionTest.class, org.olap4j.CellSetFormatterTest.class, org.olap4j.MetadataTest.class, org.olap4j.mdx.MdxTest.class, org.olap4j.transform.TransformTest.class, org.olap4j.XmlaConnectionTest.class, org.olap4j.OlapTreeTest.class, org.olap4j.OlapTest.class, }; /** * The following tests do not depend upon the driver implementation. * They should be executed once, in olap4j's test suite, not for each * provider's test suite. */ public static final Class<?>[] NON_TCK_CLASSES = { org.olap4j.impl.ConnectStringParserTest.class, org.olap4j.impl.Olap4jUtilTest.class, org.olap4j.impl.Base64Test.class, org.olap4j.test.ParserTest.class, org.olap4j.test.ArrayMapTest.class, org.olap4j.driver.xmla.cache.XmlaShaEncoderTest.class, org.olap4j.driver.xmla.proxy.XmlaCookieManagerTest.class, org.olap4j.driver.xmla.proxy.XmlaCachedProxyTest.class, }; private final Tester tester; private final Properties properties; /** * Intentionally private: use {@link #instance()}. */ private TestContext() { this(getStaticTestProperties()); } private TestContext(Properties properties) { assert properties != null; this.properties = properties; this.tester = createTester(this, properties); } /** * Adds all of the test classes in the TCK (Test Compatibility Kit) * to a given junit test suite. * * @param suite Suite to which to add tests */ private static void addTck(TestSuite suite) { for (Class<?> tckClass : TCK_CLASSES) { suite.addTestSuite(tckClass); } } /** * Converts a string constant into platform-specific line endings. * * @param string String where line endings are represented as linefeed "\n" * @return String where all linefeeds have been converted to * platform-specific (CR+LF on Windows, LF on Unix/Linux) */ public static SafeString fold(String string) { if (!NL.equals("\n")) { string = Olap4jUtil.replace(string, "\n", NL); } if (string == null) { return null; } else { return new SafeString(string); } } /** * Reverses the effect of {@link #fold}; converts platform-specific line * endings in a string info linefeeds. * * @param string String where all linefeeds have been converted to * platform-specific (CR+LF on Windows, LF on Unix/Linux) * @return String where line endings are represented as linefeed "\n" */ public static String unfold(String string) { if (!NL.equals("\n")) { string = Olap4jUtil.replace(string, NL, "\n"); } if (string == null) { return null; } else { return string; } } /** * Converts an MDX parse tree to an MDX string * * @param node Parse tree * @return MDX string */ public static String toString(ParseTreeNode node) { StringWriter sw = new StringWriter(); ParseTreeWriter parseTreeWriter = new ParseTreeWriter(sw); node.unparse(parseTreeWriter); return sw.toString(); } /** * Formats a {@link org.olap4j.CellSet}. * * @param cellSet Cell set * @return String representation of cell set */ public static String toString(CellSet cellSet) { StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw); new TraditionalCellSetFormatter().format(cellSet, pw); pw.flush(); return sw.toString(); } /** * The default instance of TestContext. * * @return default TestContext */ public static TestContext instance() { return THREAD_INSTANCE.get(); } /** * Checks that an actual string matches an expected string. If they do not, * throws a {@link junit.framework.ComparisonFailure} and prints the * difference, including the actual string as an easily pasted Java string * literal. * * @param expected Expected string * @param actual Actual string returned by test case */ public static void assertEqualsVerbose(String expected, String actual) { assertEqualsVerbose(expected, actual, true, null); } /** * Checks that an actual string matches an expected string. * * <p>If they do not, throws a {@link ComparisonFailure} and prints the * difference, including the actual string as an easily pasted Java string * literal. * * @param expected Expected string * @param actual Actual string * @param java Whether to generate actual string as a Java string literal * if the values are not equal * @param message Message to display, optional */ public static void assertEqualsVerbose(String expected, String actual, boolean java, String message) { assertEqualsVerbose(fold(expected), actual, java, message); } /** * Checks that an actual string matches an expected string. If they do not, * throws a {@link junit.framework.ComparisonFailure} and prints the * difference, including the actual string as an easily pasted Java string * literal. * * @param safeExpected Expected string, where all line endings have been * converted into platform-specific line endings * @param actual Actual string returned by test case * @param java Whether to print the actual value as a Java string literal * if the strings differ * @param message Message to print if the strings differ */ public static void assertEqualsVerbose(SafeString safeExpected, String actual, boolean java, String message) { String expected = safeExpected == null ? null : safeExpected.s; if ((expected == null) && (actual == null)) { return; } if ((expected != null) && expected.equals(actual)) { return; } if (message == null) { message = ""; } else { message += NL; } message += "Expected:" + NL + expected + NL + "Actual:" + NL + actual + NL; if (java) { message += "Actual java:" + NL + toJavaString(actual) + NL; } throw new ComparisonFailure(message, expected, actual); } /** * Converts a string (which may contain quotes and newlines) into a java * literal. * * <p>For example, <code> * <pre>string with "quotes" split * across lines</pre> * </code> becomes <code> * <pre>"string with \"quotes\" split" + NL + * "across lines"</pre> * </code> */ static String toJavaString(String s) { // Convert [string with "quotes" split // across lines] // into ["string with \"quotes\" split\n" // + "across lines // s = Olap4jUtil.replace(s, "\\", "\\\\"); s = Olap4jUtil.replace(s, "\"", "\\\""); s = LineBreakPattern.matcher(s).replaceAll(lineBreak2); s = TabPattern.matcher(s).replaceAll("\\\\t"); s = "\"" + s + "\""; String spurious = NL + indent + "+ \"\""; if (s.endsWith(spurious)) { s = s.substring(0, s.length() - spurious.length()); } if (s.indexOf(lineBreak3) >= 0) { s = "fold(" + NL + indent + s + ")"; } return s; } /** * Quotes a pattern. */ public static String quotePattern(String s) { s = s.replaceAll("\\\\", "\\\\"); s = s.replaceAll("\\.", "\\\\."); s = s.replaceAll("\\+", "\\\\+"); s = s.replaceAll("\\{", "\\\\{"); s = s.replaceAll("\\}", "\\\\}"); s = s.replaceAll("\\|", "\\\\||"); s = s.replaceAll("[$]", "\\\\\\$"); s = s.replaceAll("\\?", "\\\\?"); s = s.replaceAll("\\*", "\\\\*"); s = s.replaceAll("\\(", "\\\\("); s = s.replaceAll("\\)", "\\\\)"); s = s.replaceAll("\\[", "\\\\["); s = s.replaceAll("\\]", "\\\\]"); return s; } /** * Factory method for the {@link Tester} * object which determines which driver to test. * * @param testContext Test context * @param testProperties Properties that define the properties of the tester * @return a new Tester */ private static Tester createTester(TestContext testContext, Properties testProperties) { String helperClassName = testProperties.getProperty(Property.HELPER_CLASS_NAME.path); if (helperClassName == null) { helperClassName = "org.olap4j.XmlaTester"; if (!testProperties.containsKey(TestContext.Property.XMLA_CATALOG_URL.path)) { testProperties.setProperty(TestContext.Property.XMLA_CATALOG_URL.path, "dummy_xmla_catalog_url"); } } Tester tester; try { Class<?> clazz = Class.forName(helperClassName); final Constructor<?> constructor = clazz.getConstructor(TestContext.class); tester = (Tester) constructor.newInstance(testContext); } catch (ClassNotFoundException e) { throw new RuntimeException(e); } catch (IllegalAccessException e) { throw new RuntimeException(e); } catch (InstantiationException e) { throw new RuntimeException(e); } catch (NoSuchMethodException e) { throw new RuntimeException(e); } catch (InvocationTargetException e) { throw new RuntimeException(e); } // Apply a wrapper, if the "org.olap4j.test.wrapper" property is // specified. String wrapperName = testProperties.getProperty(Property.WRAPPER.path); Wrapper wrapper; if (wrapperName == null || wrapperName.equals("")) { wrapper = Wrapper.NONE; } else { try { wrapper = Enum.valueOf(Wrapper.class, wrapperName); } catch (IllegalArgumentException e) { throw new IllegalArgumentException("Unknown wrapper value '" + wrapperName + "'"); } } switch (wrapper) { case NONE: break; case DBCP: final BasicDataSource dataSource = new BasicDataSource(); dataSource.setDriverClassName(tester.getDriverClassName()); dataSource.setUrl(tester.getURL()); // need access to underlying connection so that we can call // olap4j-specific methods dataSource.setAccessToUnderlyingConnectionAllowed(true); tester = new DelegatingTester(tester) { public Connection createConnection() throws SQLException { return dataSource.getConnection(); } public Wrapper getWrapper() { return Wrapper.DBCP; } }; break; } return tester; } /** * Creates a test suite that executes the olap4j TCK with the given * set of properties. The properties are the same as those you can put in * {@code "test.properties"}. * * @param properties Properties * @param name Name of test suite * @return Test suite that executes the TCK */ public static TestSuite createTckSuite(Properties properties, String name) { TestContext testContext = new TestContext(properties); THREAD_INSTANCE.set(testContext); try { final TestSuite suite = new TestSuite(); suite.setName(name); addTck(suite); return suite; } finally { THREAD_INSTANCE.remove(); } } public Properties getProperties() { return properties; } /** * Enumeration of valid values for the * {@link org.olap4j.test.TestContext.Property#WRAPPER} property. */ public enum Wrapper { /** * No wrapper. */ NONE { public <T extends Statement> T unwrap(Statement statement, Class<T> clazz) throws SQLException { return ((OlapWrapper) statement).unwrap(clazz); } public <T extends Connection> T unwrap(Connection connection, Class<T> clazz) throws SQLException { return ((OlapWrapper) connection).unwrap(clazz); } }, /** * Instructs the olap4j testing framework to wrap connections using * the Apache commons-dbcp connection-pooling framework. */ DBCP { public <T extends Statement> T unwrap(Statement statement, Class<T> clazz) throws SQLException { return clazz.cast(((DelegatingStatement) statement).getInnermostDelegate()); } public <T extends Connection> T unwrap(Connection connection, Class<T> clazz) throws SQLException { return clazz.cast(((DelegatingConnection) connection).getInnermostDelegate()); } }; /** * Removes wrappers from a statement. * * @param statement Statement * @param clazz Desired result type * @return Unwrapped object * @throws SQLException on database error */ public abstract <T extends Statement> T unwrap(Statement statement, Class<T> clazz) throws SQLException; /** * Removes wrappers from a connection. * * @param connection Connection * @param clazz Desired result type * @return Unwrapped object * @throws SQLException on database error */ public abstract <T extends Connection> T unwrap(Connection connection, Class<T> clazz) throws SQLException; } /** * Returns an object containing all properties needed by the test suite. * * <p>Consists of system properties, overridden by the contents of * "test.properties" in the current directory, if it exists, and * in any parent or ancestor directory. This allows you to invoke the * test from any sub-directory of the source root and still pick up the * right test parameters. * * @return object containing properties needed by the test suite */ private static synchronized Properties getStaticTestProperties() { if (testProperties == null) { testProperties = new Properties(System.getProperties()); File dir = new File(System.getProperty("user.dir")); while (dir != null) { File file = new File(dir, "test.properties"); if (file.exists()) { try { testProperties.load(new FileInputStream(file)); } catch (IOException e) { // ignore } } file = new File(new File(dir, "olap4j"), "test.properties"); if (file.exists()) { try { testProperties.load(new FileInputStream(file)); } catch (IOException e) { // ignore } } dir = dir.getParentFile(); } } return testProperties; } /** * Converts a {@link Throwable} to a stack trace. */ public static String getStackTrace(Throwable e) { StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw); e.printStackTrace(pw); pw.flush(); return sw.toString(); } /** * Shorthand way to convert array of names into segment list. * * @param names Array of names * @return Segment list */ public static List<IdentifierSegment> nameList(String... names) { return IdentifierNode.ofNames(names).getSegmentList(); } /** * Checks that an exception is not null and the stack trace contains a * given string. Fails otherwise. * * @param throwable Stack trace * @param pattern Seek string */ public static void checkThrowable(Throwable throwable, String pattern) { if (throwable == null) { Assert.fail("query did not yield an exception"); } String stackTrace = getStackTrace(throwable); if (stackTrace.indexOf(pattern) < 0) { Assert.fail("error does not match pattern '" + pattern + "'; error is [" + stackTrace + "]"); } } /** * Returns this context's tester. * * @return a tester */ public Tester getTester() { return tester; } /** * Abstracts the information about specific drivers and database instances * needed by this test. This allows the same test suite to be used for * multiple implementations of olap4j. * * <p>Must have a public constructor that takes a * {@link org.olap4j.test.TestContext} as parameter. */ public interface Tester { /** * Returns the test context. * * @return Test context */ TestContext getTestContext(); /** * Creates a connection * * @return connection * @throws SQLException on error */ Connection createConnection() throws SQLException; /** * Returns the prefix of URLs recognized by this driver, for example * "jdbc:mondrian:" * * @return URL prefix */ String getDriverUrlPrefix(); /** * Returns the class name of the driver, for example * "mondrian.olap4j.MondrianOlap4jDriver". * * @return driver class name */ String getDriverClassName(); /** * Creates a connection using the * {@link java.sql.DriverManager#getConnection(String, String, String)} * method. * * @return connection * @throws SQLException on error */ Connection createConnectionWithUserPassword() throws SQLException; /** * Returns the URL of the FoodMart database. * * @return URL of the FoodMart database */ String getURL(); /** * Returns an enumeration indicating the driver (or strictly, the family * of drivers) supported by this Tester. Allows the test suite to * disable tests or expect slightly different results if the * capabilities of OLAP servers are different. * * @return Flavor of driver/OLAP engine we are connecting to */ Flavor getFlavor(); /** * Returns a description of the wrapper, if any, around this connection. */ Wrapper getWrapper(); enum Flavor { MONDRIAN, XMLA, REMOTE_XMLA } } /** * Implementation of {@link Tester} that delegates to an underlying tester. */ public static abstract class DelegatingTester implements Tester { protected final Tester tester; /** * Creates a DelegatingTester. * * @param tester Underlying tester to which calls are delegated */ protected DelegatingTester(Tester tester) { this.tester = tester; } public TestContext getTestContext() { return tester.getTestContext(); } public Connection createConnection() throws SQLException { return tester.createConnection(); } public String getDriverUrlPrefix() { return tester.getDriverUrlPrefix(); } public String getDriverClassName() { return tester.getDriverClassName(); } public Connection createConnectionWithUserPassword() throws SQLException { return tester.createConnectionWithUserPassword(); } public String getURL() { return tester.getURL(); } public Flavor getFlavor() { return tester.getFlavor(); } public Wrapper getWrapper() { return tester.getWrapper(); } } /** * Enumeration of system properties that mean something to the olap4j * testing framework. */ public enum Property { /** * Name of the class used by the test infrastructure to make connections * to the olap4j data source and perform other housekeeping operations. * Valid values include "mondrian.test.MondrianOlap4jTester" (the * default, per test.properties) and "org.olap4j.XmlaTester". */ HELPER_CLASS_NAME("org.olap4j.test.helperClassName"), /** * Test property that provides the value of returned by the * {@link Tester#getURL} method. */ CONNECT_URL("org.olap4j.test.connectUrl"), /** * Test property that provides the URL name of the catalog for the XMLA * driver. */ XMLA_CATALOG_URL("org.olap4j.XmlaTester.CatalogUrl"), /** * Test property related to the remote XMLA tester. * Must be a valid XMLA driver URL. */ REMOTE_XMLA_URL("org.olap4j.RemoteXmlaTester.JdbcUrl"), /** * Test property related to the remote XMLA tester. * User name to use. */ REMOTE_XMLA_USERNAME("org.olap4j.RemoteXmlaTester.Username"), /** * Test property related to the remote XMLA tester. * Password to use. */ REMOTE_XMLA_PASSWORD("org.olap4j.RemoteXmlaTester.Password"), /** * Test property that indicates the wrapper to place around the * connection. Valid values are defined by the {@link Wrapper} * enumeration, such as "Dbcp". If not specified, the connection is used * without a connection pool. */ WRAPPER("org.olap4j.test.wrapper"),; public final String path; /** * Creates a Property enum value. * * @param path Full name of property, e.g. "org.olap4.foo.Bar". */ private Property(String path) { this.path = path; } } /** * Wrapper around a string that indicates that all line endings have been * converted to platform-specific line endings. * * @see TestContext#fold */ public static class SafeString { public final String s; /** * Creates a SafeString. * @param s String */ private SafeString(String s) { this.s = s; } } } // End TestContext.java