/******************************************************************************* * Copyright (c) 2009 Cambridge Semantics Incorporated. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Cambridge Semantics Incorporated - initial API and implementation *******************************************************************************/ package org.openanzo.test.client.cli; import java.io.ByteArrayOutputStream; import java.io.InputStream; import java.io.PrintStream; import java.io.StringReader; import java.security.Permission; import java.util.ArrayList; import java.util.List; import org.apache.commons.io.IOUtils; import org.openanzo.client.AnzoClient; import org.openanzo.client.cli.CommandLineInterface; import org.openanzo.rdf.RDFFormat; import org.openanzo.rdf.Statement; import org.openanzo.rdf.utils.ReadWriteUtils; import org.openanzo.rdf.utils.SmartEncodingInputStream; import org.openanzo.test.AbstractTest; /** * Tests the command line interface commands against a live repository. * * @author Joe Betz <jpbetz@cambridgesemantics.com> * */ public class TestCommandLineInterface extends AbstractTest { // directory containing test data static final String PATH = "../org.openanzo.test/src/test/resources/org/openanzo/test/client/cli"; // select a CLI user settings file for the appropriate environment static String SETTINGS; static { String env = System.getProperty(TEST_ENVIRONMENT_PROPERTY); if (env != null && env.equals(REGRESSION)) { SETTINGS = PATH + "/settings-regress.trig"; } else { SETTINGS = PATH + "/settings.trig"; } } static String SETTINGS_LDAP; static { String env = System.getProperty(TEST_ENVIRONMENT_PROPERTY); if (env != null && env.equals(REGRESSION)) { SETTINGS_LDAP = PATH + "/settings-ldap-regress.trig"; } else { SETTINGS_LDAP = PATH + "/settings-ldap.trig"; } } /** * Verify RDF sent to STDIN can be converted to RDF and sent to STDOUT. * * @throws Exception */ public void testConvertStdin() throws Exception { verifyTextOutput("convert -z " + SETTINGS + " -x -o rdf", "convert-input.trig", "convert-output.rdf"); } /** * Verify RDF in a file can be read and sent to STDOUT. * * @throws Exception */ public void testConvertFile() throws Exception { verifyOutput("convert -z " + SETTINGS + " -x -o trix " + PATH + "/convert-file-input.trig", null, "convert-file-output.trix", RDFFormat.TRIX); } /** * Verify RDF in a file can converted to a TriX file correctly. * * @throws Exception */ public void testConvertTurtleFile() throws Exception { verifyOutput("convert -z " + SETTINGS + " -x -g clitest:g2 -o trix " + PATH + "/convert-turtle-file-input.ttl", null, "convert-file-output.trix", RDFFormat.TRIX); } /** * Verify that an input file format without support for named graphs can be converted without having to specify a default graph URI. See * http://www.openanzo.org/projects/openanzo/ticket/566 * * @throws Exception */ public void testConvertNoDefaultGraph() throws Exception { verifyOutput("convert -z " + SETTINGS + " -x -o nt " + PATH + "/convert-nograph-input.rdf", null, "convert-nograph-output.nt", RDFFormat.NTRIPLES); } /** * Verify that both CURIEs and URIs are collapsed only if they are in the prefix map * * @throws Exception */ public void testCollapse() throws Exception { verifyTextOutput("collapse -z " + SETTINGS + " http://cambridgesemantics.com/cli-tests#a clitest:b http://cambridgesemantics.com/unprefixed#a invalid:prefix", null, "collapse-output.txt"); } /** * Verify that both CURIEs and URIs are collapsed only if they are in the prefix map * * @throws Exception */ public void testExpand() throws Exception { verifyTextOutput("expand -z " + SETTINGS + " http://cambridgesemantics.com/cli-tests#a clitest:b http://cambridgesemantics.com/unprefixed#a invalid:prefix", null, "expand-output.txt"); } /** * Verify that we warn the user about common schemes * * @throws Exception */ public void testWarnCommonSchemes() throws Exception { verifyTextOutput("expand -z " + SETTINGS_LDAP + " ldap:foo", null, "warn-output.txt", "warn-error.txt"); } /** * Verify that RDF can be read from STDIN and can be sent to the repository to create a graph. * * Also test a expanded URI input * * @throws Exception */ public void testCreateStdin() throws Exception { reset(); verifySuccessStatus("create -z " + SETTINGS + " ", "create-stdin-input.trig"); verifyOutput("get -z " + SETTINGS + " http://cambridgesemantics.com/cli-tests#create", null, "create-stdin-output.trig", RDFFormat.TRIG); } /** * Verify that RDF can be read from a Turtle file and be sent to the repository to create a graph. Since Turtle does not support named graphs, a named graph * uri is provided on the command line. * * @throws Exception */ public void testCreateFile() throws Exception { reset(); verifySuccessStatus("create -z " + SETTINGS + " -g clitest:create-file " + PATH + "/create-file-input.ttl", null); verifyOutput("get -z " + SETTINGS + " clitest:create-file", null, "create-file-output.trig", RDFFormat.TRIG); } /** * Verify that a file containing relative URIs is imported correctly when the base option is set. * * @throws Exception */ public void testImportWithBase() throws Exception { reset(); verifySuccessStatus("import -z " + SETTINGS + " --base http://cambridgesemantics.com/base/ " + PATH + "/import-with-base-input.trig", null); verifyOutput("get -z " + SETTINGS + " clitest:import", null, "import-with-base-output.trig", RDFFormat.TRIG); } /** * Verify that a replace command can be executed using rdf provided via STDIN. * * @throws Exception */ public void testReplaceStdin() throws Exception { reset(); verifySuccessStatus("create -z " + SETTINGS + " -g clitest:create-file " + PATH + "/create-file-input.ttl", null); verifySuccessStatus("replace -z " + SETTINGS + " " + PATH + "/replace-stdin-input.trig", null); verifyOutput("get -z " + SETTINGS + " clitest:create-file", null, "replace-stdin-input.trig", RDFFormat.TRIG); } /** * Verify that a replace command can be executed using rdf from a file. * * Include bnodes in initial create to verify they are handled properly. * * See openanzo ticket #358 * * @throws Exception */ public void testReplaceFile() throws Exception { reset(); verifySuccessStatus("create -z " + SETTINGS + " " + PATH + "/replace-file-initial.trig", null); verifySuccessStatus("replace -z " + SETTINGS + " " + PATH + "/replace-stdin-input.trig", null); verifyOutput("get -z " + SETTINGS + " clitest:create-file", null, "replace-stdin-input.trig", RDFFormat.TRIG); } /** * Verify that an error is thrown if a replace call is attempted on a graph that does not exist. * * @throws Exception */ public void testIllegalReplaceFile() throws Exception { reset(); verifyFailureStatus("replace -z " + SETTINGS, "replace-stdin-input.trig"); } /** * Verify that the '-f' flag will force a graph to be created if it does not exist. * * @throws Exception */ public void testForceReplaceFile() throws Exception { reset(); verifySuccessStatus("replace -z " + SETTINGS + " -f", "replace-stdin-input.trig"); verifyOutput("get -z " + SETTINGS + " clitest:create-file", null, "replace-stdin-input.trig", RDFFormat.TRIG); } /** * Verify that non-reserved predicates in a metadata graph can be altered by the replace command. * * See openanzo ticket #742 * * @throws Exception */ public void testReplaceMetadataGraph() throws Exception { reset(); verifySuccessStatus("create -z " + SETTINGS + " " + PATH + "/replace-stdin-input.trig", null); verifySuccessStatus("replace -z " + SETTINGS + " " + PATH + "/replace-metadata-input.trig", null); verifyOutput("find -z " + SETTINGS + " -sub clitest:create-file -pred anzo:canBeReadBy", null, "replace-metadata-output.trig", RDFFormat.TRIG); } /** * Run a query from the arguments of the command. Also make sure the -a flag runs the query against a default graph containing all the named graphs on the * server. * * @throws Exception */ public void testQueryFromArguments() throws Exception { reset(); verifySuccessStatus("create -z " + SETTINGS + " " + PATH + "/query-data.trig", null); verifyTextOutput("query -z " + SETTINGS + " -a -o srx SELECT ?o WHERE { clitest:query dc:title ?o } ", null, "query-arguments.srx"); } /** * Run a CONSTRUCT query from a file. * * @throws Exception */ public void testQueryFromFile() throws Exception { reset(); verifySuccessStatus("create -z " + SETTINGS + " " + PATH + "/query-data.trig", null); verifyOutput("query -z " + SETTINGS + " -f " + PATH + "/query-from-file.rq ", null, "query-from-file.trig", RDFFormat.TRIG); } /** * Run a CONSTRUCT query from a file. * * @throws Exception */ public void testNonRevisionedQueryFromFile() throws Exception { reset(); verifySuccessStatus("create -nr -z " + SETTINGS + " " + PATH + "/query-data.trig", null); verifyOutput("query -z " + SETTINGS + " -f " + PATH + "/query-from-file.rq ", null, "query-from-file.trig", RDFFormat.TRIG); } /** * Run a CONSTRUCT query from STDIN. * * @throws Exception */ public void testQueryFromStdin() throws Exception { reset(); verifySuccessStatus("create -z " + SETTINGS + " " + PATH + "/query-data.trig", null); verifyOutput("query -z " + SETTINGS + " ", "query-from-file.rq", "query-from-file.trig", RDFFormat.TRIG); } /** * Run a CONSTRUCT query against a local file that does not support named graphs. * * @throws Exception */ public void testQueryLocalTurtleFileFromArguments() throws Exception { reset(); verifyOutput("query -z " + SETTINGS + " -d " + PATH + "/query-data.trig CONSTRUCT { clitest:custom dc:title ?o } WHERE { clitest:query dc:source ?o }", null, "query-from-file.trig", RDFFormat.TRIG); } /** * Remove a graph and make sure it's gone. * * @throws Exception */ public void testRemove() throws Exception { reset(); verifySuccessStatus("create -z " + SETTINGS + " -g clitest:create-file " + PATH + "/create-file-input.ttl", null); verifySuccessStatus("remove -z " + SETTINGS + " clitest:create-file", null); verifyCommand("get -z " + SETTINGS + " clitest:create-file", null, "empty.txt", null, true); //verifyTextOutput(, null, "empty.txt"); } /** * Update a graph with a file of additions and a file of removals and verify the update is applied correctly. * * @throws Exception */ public void testUpdate() throws Exception { reset(); verifySuccessStatus("create -z " + SETTINGS + " -g clitest:create-file " + PATH + "/create-file-input.ttl", null); verifySuccessStatus("update -z " + SETTINGS + " -a " + PATH + "/update-additions.trig -r " + PATH + "/update-removals.trig", null); verifyOutput("get -z " + SETTINGS + " clitest:create-file", null, "update-results.trig", RDFFormat.TRIG); } /** * Check that an error occurs trying to update a graph that does not exist. * * @throws Exception */ public void testIllegalUpdate() throws Exception { reset(); verifyFailureStatus("update -z " + SETTINGS + " -a " + PATH + "/update-additions.trig -r " + PATH + "/update-removals.trig", null); } /** * Check that the '-f' flag will force a graph to be created if it does not exist. * * @throws Exception */ public void testForceUpdate() throws Exception { reset(); verifySuccessStatus("update -z " + SETTINGS + " -f -a " + PATH + "/update-additions.trig", null); verifyOutput("get -z " + SETTINGS + " clitest:create-file", null, "update-additions.trig", RDFFormat.TRIG); } /** * Check that the update command does removes before adds. See http://www.openanzo.org/projects/openanzo/ticket/743 * * @throws Exception */ public void testUpdateRemoveAndAddOrder() throws Exception { reset(); verifySuccessStatus("update -z " + SETTINGS + " -f -a " + PATH + "/update-additions.trig -r " + PATH + "/update-additions.trig", null); verifyOutput("get -z " + SETTINGS + " clitest:create-file", null, "update-additions.trig", RDFFormat.TRIG); } /** * Check that a anzo 'dataset' graph is correctly expanded to a dataset. * * @throws Exception */ public void testExpandDataset() throws Exception { reset(); verifySuccessStatus("create -z " + SETTINGS + " " + PATH + "/expand-dataset-initial.trig", null); verifyOutput("get --expand-dataset -z " + SETTINGS + " clitest:dataset", null, "expand-dataset-output.trig", RDFFormat.TRIG); } /** * Verify a simple semantic service executes correctly. * * @throws Exception */ public void testCall() throws Exception { verifyOutput("call -z " + SETTINGS + " echo:echo " + PATH + "/echo-request.trig", null, "echo-request.trig", RDFFormat.TRIG); } /** * Test union command * * @throws Exception */ public void testUnion() throws Exception { verifyOutput("union -z " + SETTINGS + " " + PATH + "/union-input-1.trig " + PATH + "/union-input-2.trig", null, "union-output.trig", RDFFormat.TRIG); } /** * Test reset command * * @throws Exception */ public void testReset() throws Exception { reset(); verifySuccessStatus("create -z " + SETTINGS + " " + PATH + "/query-data.trig", null); verifySuccessStatus("reset -z " + SETTINGS + " " + PATH + "/reset-input.trig", null); verifyOutput("get -z " + SETTINGS + " clitest:query", null, "empty.txt", RDFFormat.TRIG, true); } void reset() throws Exception { AnzoClient client = new AnzoClient(getDefaultClientConfiguration()); try { client.connect(); client.reset(loadStatements("initialize.trig"), null); } finally { client.close(); } } private void verifySuccessStatus(String command, String stdinFile) throws Exception { verifyCommand(command, stdinFile, null, null, false); } private void verifyFailureStatus(String command, String stdinFile) throws Exception { verifyCommand(command, stdinFile, null, null, true); } private void verifyTextOutput(String command, String stdinFile, String expectedStdoutFile) throws Exception { verifyCommand(command, stdinFile, expectedStdoutFile, null, false); } private void verifyTextOutput(String command, String stdinFile, String expectedStdoutFile, String expectedStderrFile) throws Exception { verifyCommand(command, stdinFile, expectedStdoutFile, expectedStderrFile, false); } /** * Run a command and verify it's outputs match the contents of the given files for the given filenames. */ private void verifyCommand(String command, String stdinFile, String expectedStdoutFile, String expectedStderrFile, boolean expectFailure) throws Exception { TestCommandResult result; try { result = runCommandTest(command, stdinFile == null ? IOUtils.toInputStream("") : TestCommandLineInterface.class.getResourceAsStream(stdinFile)); } catch (Exception t) { if (expectFailure) return; throw t; } if (expectFailure) { if (result.status == 0) { throw new IllegalStateException("Command Line Interface returned status code 0, expected non-zero: " + result.status); } return; } if (result.status != 0) { throw new IllegalStateException("Command Line Interface returned a non-zero status code (" + result.status + "), expected 0: " + result.error); } if (expectedStdoutFile != null) { assertEquals(IOUtils.toString(TestCommandLineInterface.class.getResourceAsStream(expectedStdoutFile)), result.output); } if (expectedStderrFile != null) { assertEquals(IOUtils.toString(TestCommandLineInterface.class.getResourceAsStream(expectedStderrFile)), result.error); } } private void verifyOutput(String command, String stdinFile, String expectedStdoutRdfFile, RDFFormat expectedFormat) throws Exception { verifyOutput(command, stdinFile, expectedStdoutRdfFile, expectedFormat, false); } /** * Run a command and verify it's RDF output matches the contents of the RDF in the expectedFile. */ private void verifyOutput(String command, String stdinFile, String expectedStdoutRdfFile, RDFFormat expectedFormat, boolean expectFailure) throws Exception { TestCommandResult result; try { result = runCommandTest(command, stdinFile == null ? IOUtils.toInputStream("") : TestCommandLineInterface.class.getResourceAsStream(stdinFile)); } catch (Exception t) { if (expectFailure) return; throw t; } if (expectFailure) { if (result.status == 0) { throw new IllegalStateException("Command Line Interface returned status code 0, expected non-zero: " + result.status); } return; } if (result.status != 0) { throw new IllegalStateException("Command Line Interface returned a non-zero status code (" + result.status + "), expected 0: " + result.error); } List<Statement> out = new ArrayList<Statement>(ReadWriteUtils.loadStatements(new StringReader(result.output), expectedFormat, "")); List<Statement> expected = new ArrayList<Statement>(ReadWriteUtils.loadStatements(SmartEncodingInputStream.createSmartReader(TestCommandLineInterface.class.getResourceAsStream(expectedStdoutRdfFile)), expectedFormat, "")); assertEquals(expected.size(), out.size()); for (Statement stmt : expected) { int index = out.indexOf(stmt); assertTrue("statement missing from output" + stmt, index >= 0); Statement actualStmt = out.get(index); assertEquals(actualStmt.getNamedGraphUri(), stmt.getNamedGraphUri()); // we want exact matches, so check the named graph explicitly } } /** * Isolates a java method call to the command line interface's main method. Mock System in, out and error are put around the method call and System.exit's * are managed and their status captured. * * The System out, error and exit status are returned. * */ private TestCommandResult runCommandTest(String command, InputStream input) throws Exception { InputStream defaultInput = System.in; PrintStream defaultOutput = System.out; PrintStream defaultError = System.err; SecurityManager defaultSecurityManager = System.getSecurityManager(); int status = -1; String output = null; String error = null; try { System.setIn(input); ByteArrayOutputStream out = new ByteArrayOutputStream(); System.setOut(new PrintStream(new PrintStream(out))); ByteArrayOutputStream err = new ByteArrayOutputStream(); System.setErr(new PrintStream(new PrintStream(err))); System.setSecurityManager(new ExitStatusManager()); try { CommandLineInterface.main(command.split("\\s+")); } catch (ExitStatusException e) { status = e.getStatus(); } output = out.toString("UTF-8"); error = err.toString("UTF-8"); } finally { System.setIn(defaultInput); System.setOut(defaultOutput); System.setErr(defaultError); System.setSecurityManager(defaultSecurityManager); } return new TestCommandResult(output, error, status); } /** * Tracks the output of a command invocation. * * @author Joe Betz <jpbetz@cambridgesemantics.com> * */ private static class TestCommandResult { public String output; public String error; public int status; public TestCommandResult(String output, String error, int status) { this.output = output; this.error = error; this.status = status; } } /** * For testing command line interface. * * Custom security manager specifically for catching System.exit() calls and converting them into an exception which can be caught. * * @author Joe Betz <jpbetz@cambridgesemantics.com> * */ private static class ExitStatusManager extends SecurityManager { @Override public void checkExit(int status) { throw new ExitStatusException(status); } @Override public void checkPermission(Permission perm) { } } /** * Exception to work with StopExitManager. * * @author Joe Betz <jpbetz@cambridgesemantics.com> * */ private static class ExitStatusException extends SecurityException { private static final long serialVersionUID = 1L; int status; public ExitStatusException(int status) { this.status = status; } public int getStatus() { return status; } } }