Java tutorial
/* * GRAKN.AI - THE KNOWLEDGE GRAPH * Copyright (C) 2018 Grakn Labs Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <https://www.gnu.org/licenses/>. */ package grakn.core.console.test; import com.google.auto.value.AutoValue; import com.google.common.base.StandardSystemProperty; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Lists; import com.google.common.collect.Sets; import grakn.core.common.util.GraknVersion; import grakn.core.console.GraknConsole; import grakn.core.rule.GraknTestServer; import graql.lang.Graql; import io.grpc.Status; import org.apache.commons.io.output.TeeOutputStream; import org.hamcrest.Matcher; import org.junit.AfterClass; import org.junit.Before; import org.junit.BeforeClass; import org.junit.ClassRule; import org.junit.Ignore; import org.junit.Test; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.PrintStream; import java.nio.file.Files; import java.util.List; import java.util.stream.Stream; import static java.util.stream.Collectors.joining; import static java.util.stream.Collectors.toList; import static org.hamcrest.CoreMatchers.allOf; import static org.hamcrest.CoreMatchers.anyOf; import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.not; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.anything; import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.endsWith; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; public class GraknConsoleIT { @ClassRule public static final GraknTestServer server = new GraknTestServer(); private static InputStream trueIn; private static int keyspaceSuffix = 0; private final static String analyticsDataset = "define obj sub entity, plays rol; rel sub relation, relates rol; " + "insert $a isa obj; $b isa obj; $c isa obj; $d isa obj; " + "(rol: $a, rol: $b) isa rel; (rol: $a, rol: $c) isa rel; (rol: $a, rol: $d) isa rel; "; @BeforeClass public static void setUpClass() throws IOException { trueIn = System.in; System.setProperty(StandardSystemProperty.USER_HOME.key(), Files.createTempDirectory("grakn-console").toString()); } @Before public void changeSuffix() { keyspaceSuffix += 1; } @AfterClass public static void resetIO() { System.setIn(trueIn); } @Test public void when_consoleSessionExitCommand_expect_consoleTerminates() { // Assert simply that the shell starts and terminates without errors String output = runConsoleSessionWithoutExpectingErrors("exit\n"); assertTrue(output.matches("[\\s\\S]*> exit(\r\n?|\n)")); } @Test public void when_startingConsoleWithOptionHelp_expect_printHelpMessage() { String result = runConsoleSessionWithoutExpectingErrors("", "--help"); // Check for a few expected usage messages assertThat(result, allOf(containsString("usage"), containsString("grakn console [options]"), containsString("-f"), containsString("--file <arg>"), containsString("path to a Graql file"))); } @Test public void when_startingConsoleWithOptionVersion_expect_printVersion() { String result = runConsoleSessionWithoutExpectingErrors("", "--version"); assertThat(result, containsString(GraknVersion.VERSION)); } @Test public void when_writingToDefaultKeyspace_expect_successReadFromDefaultKeyspace() throws Exception { runConsoleSessionWithoutExpectingErrors("define im-in-the-default-keyspace sub entity;\ncommit\n"); assertConsoleSessionMatches(ImmutableList.of("-k", "grakn"), "match im-in-the-default-keyspace sub entity; get; count;", containsString("1")); } @Test public void when_writingToDifferentKeyspaces_expect_theyDoNotGetMixedUp() { runConsoleSessionWithoutExpectingErrors("define foo-foo sub entity;\ncommit\n", "-k", "foo"); runConsoleSessionWithoutExpectingErrors("define bar-bar sub entity;\ncommit\n", "-k", "bar"); String fooFooInFoo = runConsoleSessionWithoutExpectingErrors("match foo-foo sub entity; get; count;\n", "-k", "foo"); String barBarInBar = runConsoleSessionWithoutExpectingErrors("match bar-bar sub entity; get; count;\n", "-k", "bar"); assertThat(fooFooInFoo, containsString("1")); assertThat(barBarInBar, containsString("1")); Response barBarInFoo = runConsoleSession("match bar-bar sub entity; get; count;\n", "-k", "foo"); Response fooFooInBar = runConsoleSession("match foo-foo sub entity; get; count;\n", "-k", "bar"); assertTrue(barBarInFoo.err().contains("label 'bar-bar' not found")); assertTrue(fooFooInBar.err().contains("label 'foo-foo' not found")); } @Test public void when_startingConsoleWithOptionLoadFile_expect_noError() { Response response = runConsoleSession("", "-f", "console/test/file-(with-parentheses).gql"); assertEquals("", response.err()); } @Test public void when_loadingFileInConsoleSession_expect_dataIsWritten() throws Exception { assertConsoleSessionMatches("load console/test/file-(with-parentheses).gql", anything(), "match movie sub entity; get; count;", containsString("1")); } @Test public void when_loadingFileWithEscapes_expect_dataIsWritten() throws Exception { assertConsoleSessionMatches("load console/test/file-\\(with-parentheses\\).gql", anything(), "match movie sub entity; get; count;", containsString("1")); } @Test public void when_writingMatchQueries_expect_resultsReturned() { String[] result = runConsoleSessionWithoutExpectingErrors( "match $x sub " + Graql.Token.Type.THING + "; get;\nexit").split("\r\n?|\n"); // Make sure we find a few results (don't be too fussy about the output here) assertThat(result[4], endsWith("> match $x sub " + Graql.Token.Type.THING + "; get;")); assertTrue(result.length > 5); } @Test public void when_writingRelations_expect_dataIsWritten() throws Exception { assertConsoleSessionMatches("define name sub attribute, datatype string;", anything(), "define marriage sub relation, relates spouse;", anything(), "define person sub entity, has name, plays spouse;", anything(), "insert $_ isa person, has name \"Bill Gates\";", anything(), "insert $_ isa person, has name \"Melinda Gates\";", anything(), "match $husband isa person, has name \"Bill Gates\"; $wife isa person, has name \"Melinda Gates\"; insert (spouse: $husband, spouse: $wife) isa marriage;", anything(), "match $x isa marriage; get;", allOf(containsString("spouse"), containsString("isa"), containsString("marriage"))); } @Test public void when_writingInsertQueries_expect_dataIsWritten() throws Exception { assertConsoleSessionMatches("define entity2 sub entity;", anything(), "match $x isa entity2; get $x; count;", containsString("0"), "insert $x isa entity2;", anything(), "match $x isa entity2; get $x; count;", containsString("1")); } @Test public void when_writingInsertQueries_expect_writtenDataPrinted() throws Exception { assertConsoleSessionMatches("define X sub entity; insert $thingy isa X;", containsString("{}"), allOf(containsString("$thingy"), containsString("isa"), containsString("X"))); } @Test public void when_writingAggregateCountQuery_expect_correctCount() throws Exception { int NUM_METATYPES = 4; assertConsoleSessionMatches("match $x sub " + Graql.Token.Type.THING + "; get; count;", is(Integer.toString(NUM_METATYPES))); } @Test public void when_writingAggregateGroupQuery_expect_correctGroupingOfAnswers() throws Exception { assertConsoleSessionMatches("define name sub attribute, datatype string;", anything(), "define person sub entity, has name;", anything(), "insert $x isa person, has name \"Alice\";", anything(), "insert $x isa person, has name \"Bob\";", anything(), "match $x isa person, has name $y; get; group $x;", anyOf(containsString("Alice"), containsString("Bob")), anyOf(containsString("Alice"), containsString("Bob"))); } @Test public void when_startingConsoleWithOptionNoInfer_expect_queriesDoNotInfer() throws Exception { assertConsoleSessionMatches(ImmutableList.of("--no_infer"), "define man sub entity, has name; name sub attribute, datatype string;", anything(), "define person sub entity;", anything(), "insert $_ isa man, has name 'felix';", anything(), "define my-rule sub rule, when {$x isa man;}, then {$x isa person;};", anything(), "commit", "match $_ isa person, has name $x; get;" // No results ); } @Test public void when_startingConsoleWithoutOptionNoInfer_expect_queriesToInfer() throws Exception { assertConsoleSessionMatches("define man sub entity, has name; name sub attribute, datatype string;", anything(), "define person sub entity;", anything(), "insert $_ isa man, has name 'felix';", anything(), "match $_ isa person, has name $x; get;", // No results "define my-rule sub rule, when {$x isa man;}, then {$x isa person;};", anything(), "commit", "match $_ isa person, has name $x; get;", containsString("felix") // Results after result is added ); } @Test public void when_writingInvalidQuery_expect_errorsInResponse() { Response response = runConsoleSession( "define movie sub entity; insert $moon isa movie; $europa isa $moon;\n"); assertThat(response.err(), allOf(containsString("not"), containsString("type"))); } @Test public void when_writingComputeQueries_expect_correctCount() throws Exception { assertConsoleSessionMatches(analyticsDataset, anything(), anything(), "commit", "compute count;", is("7")); } @Test public void when_rollback_expect_transactionIsCancelled() { Response response = runConsoleSession("define E sub entity;\nrollback\nmatch $x type E; get;\n"); assertTrue(response.err().contains("label 'E' not found")); } @Test public void when_writingQueryWithLimitOne_expect_oneLineResponse() throws Exception { assertConsoleSessionMatches("match $x sub thing; get; limit 1;", anything() // Only one result ); } @Test public void when_serverIsNotRunning_expect_connectionError() { Response response = runConsoleSession("", "-r", "localhost:7654"); assertThat(response.err(), containsString(Status.Code.UNAVAILABLE.name())); } @Test public void when_committingInvalidData_expect_commitError() { Response response = runConsoleSession("insert bob sub relation;\ncommit"); assertFalse(response.out(), response.err().isEmpty()); } @Ignore("to be fixed") @Test public void when_runningCleanCommand_expect_keyspaceIsDeleted() throws Exception { assertConsoleSessionMatches("define my-type sub entity;", is("{}"), "commit", "match $x sub entity; get;", containsString("entity"), containsString("entity"), "clean", containsString("Are you sure?"), containsString("Type 'confirm' to continue"), "confirm", containsString("Cleaning keyspace"), anything(), containsString("Keyspace deleted")); } @Ignore("to be fixed") @Test public void when_cancellingCleanCommand_expect_keyspaceIsNotDeleted() throws Exception { assertConsoleSessionMatches("define my-type sub entity;", is("{}"), "match $x sub entity; get;", containsString("entity"), containsString("entity"), "clean", containsString("Are you sure?"), containsString("Type 'confirm' to continue"), "n", is("Clean command cancelled"), "match $x sub entity; get;", containsString("entity"), containsString("entity"), "clean", containsString("Are you sure?"), containsString("Type 'confirm' to continue"), "no thanks bad idea thanks for warning me", is("Clean command cancelled"), "match $x sub entity; get;", containsString("entity"), containsString("entity")); } @Test public void when_writingMultipleQueries_expect_multipleResponses() throws Exception { assertConsoleSessionMatches( "define X sub entity; insert $x isa X; match $y isa X; get; match $y isa X; get; count;", // Make sure we see results from all four queries containsString("{}"), containsString("$x"), containsString("$y"), is("1")); } @Test public void when_writingMultipleQueriesWithError_expect_multipleResponsesWithError() { Response response = runConsoleSession( "match $x sub somerandomstring; get; count;\n" + "match $x sub thing; get; count;\n"); assertThat(response.err(), not(containsString("error"))); assertThat(response.out(), containsString("1")); } @Test public void when_ErrorOccurs_expect_noStackTrace() { Response response = runConsoleSession("match fofobjiojasd\n"); assertFalse(response.out(), response.err().isEmpty()); assertThat(response.err(), not(containsString(".java"))); } private void assertConsoleSessionMatches(Object... matchers) throws Exception { assertConsoleSessionMatches(ImmutableList.of(), matchers); } // Arguments should be strings or matchers. Strings are interpreted as input, matchers as expected output private void assertConsoleSessionMatches(List<String> arguments, Object... matchers) throws Exception { String input = Stream.of(matchers).filter(String.class::isInstance).map(String.class::cast) .collect(joining("\n", "", "\n")); List<Matcher<? super String>> matcherList = Stream.of(matchers) .map(obj -> (obj instanceof Matcher) ? (Matcher<String>) obj : endsWith("> " + obj)) .collect(toList()); String output = runConsoleSessionWithoutExpectingErrors(input, arguments.toArray(new String[arguments.size()])); List<String> outputLines = Lists.newArrayList(output.replace(" \r", "").split("\n")); ImmutableSet<String> noPromptArgs = ImmutableSet.of("-e", "-f", "-b", "-v", "-h"); if (Sets.intersection(Sets.newHashSet(arguments), noPromptArgs).isEmpty()) { // Remove first four lines containing license and last line containing prompt outputLines = outputLines.subList(4, outputLines.size() - 1); } assertThat(outputLines, contains(matcherList)); } private String runConsoleSessionWithoutExpectingErrors(String input, String... args) { Response response = runConsoleSession(input, args); String errMessage = response.err(); assertTrue("Error: \"" + errMessage + "\"", errMessage.isEmpty()); return response.out(); } private Response runConsoleSession(String input, String... args) { args = addKeyspaceAndUriParams(args); OutputStream bufferOut = new ByteArrayOutputStream(); OutputStream bufferErr = new ByteArrayOutputStream(); PrintStream printOut = new PrintStream(new TeeOutputStream(bufferOut, System.out)); PrintStream printErr = new PrintStream(new TeeOutputStream(bufferErr, System.err)); try { System.setIn(new ByteArrayInputStream(input.getBytes())); GraknConsole console = new GraknConsole(args, printOut, printErr); console.run(); } catch (Exception e) { printErr.println(e.getMessage()); printErr.flush(); } finally { resetIO(); } printOut.flush(); printErr.flush(); return Response.of(bufferOut.toString(), bufferErr.toString()); } private String[] addKeyspaceAndUriParams(String[] args) { List<String> argList = Lists.newArrayList(args); int keyspaceIndex = argList.indexOf("-k") + 1; if (keyspaceIndex == 0) { argList.add("-k"); argList.add(GraknConsole.DEFAULT_KEYSPACE); keyspaceIndex = argList.size() - 1; } argList.set(keyspaceIndex, argList.get(keyspaceIndex) + keyspaceSuffix); boolean uriSpecified = argList.contains("-r"); if (!uriSpecified) { argList.add("-r"); argList.add(server.grpcUri()); } return argList.toArray(new String[argList.size()]); } @AutoValue static abstract class Response { abstract String out(); abstract String err(); static Response of(String out, String err) { return new AutoValue_GraknConsoleIT_Response(out, err); } } }