Skip to content

Commit

Permalink
Merge pull request #7896 from FlorentinD/automate-cypherdb-testing
Browse files Browse the repository at this point in the history
Automate ephermal db testing
  • Loading branch information
FlorentinD authored Oct 6, 2023
2 parents 354f4c0 + 74ec5ec commit 1101b4c
Show file tree
Hide file tree
Showing 16 changed files with 156 additions and 55 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ final class DocumentationTestToolsConstants {
static final String GRAPH_PROJECT_QUERY_ROLE = "graph-project-query";
static final String QUERY_EXAMPLE_ROLE = "query-example";
static final String TEST_TYPE_NO_RESULT = "no-result";

static final String TEST_DATABASE_ATTRIBUTE = "database";
static final String TEST_GROUP_ATTRIBUTE = "group";
static final String TEST_OPERATOR_ATTRIBUTE = "operator";
static final String ROLE_SELECTOR = "role";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,24 @@
import org.asciidoctor.SafeMode;
import org.assertj.core.api.Assertions;
import org.jetbrains.annotations.NotNull;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DynamicTest;
import org.junit.jupiter.api.TestFactory;
import org.junit.jupiter.api.io.TempDir;
import org.neo4j.gds.BaseProcTest;
import org.neo4j.dbms.api.DatabaseManagementService;
import org.neo4j.gds.QueryRunner;
import org.neo4j.gds.compat.CompatUserAggregationFunction;
import org.neo4j.gds.compat.GraphDatabaseApiProxy;
import org.neo4j.gds.compat.Neo4jProxy;
import org.neo4j.gds.core.Settings;
import org.neo4j.gds.core.loading.GraphStoreCatalog;
import org.neo4j.gds.doc.syntax.DocQuery;
import org.neo4j.graphdb.GraphDatabaseService;
import org.neo4j.graphdb.Result;
import org.neo4j.io.layout.Neo4jLayout;
import org.neo4j.test.TestDatabaseManagementServiceBuilder;
import org.neo4j.test.extension.Inject;

import java.io.File;
import java.nio.file.Path;
Expand All @@ -44,28 +53,43 @@
import java.util.function.Consumer;
import java.util.stream.Collectors;

import static java.util.Collections.singletonList;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.AssertionsForClassTypes.assertThatNoException;
import static org.junit.jupiter.api.DynamicTest.dynamicTest;
import static org.neo4j.gds.compat.GraphDatabaseApiProxy.registerFunctions;
import static org.neo4j.gds.compat.GraphDatabaseApiProxy.registerProcedures;

public abstract class MultiFileDocTestBase extends BaseProcTest {
public abstract class MultiFileDocTestBase {
private List<DocQuery> beforeEachQueries;

private List<DocQuery> beforeAllQueries;

private List<QueryExampleGroup> queryExampleGroups;

@Inject
protected DatabaseManagementService dbms;

protected GraphDatabaseService defaultDb;

protected abstract List<String> adocPaths();

@BeforeEach
void setUp(@TempDir File workingDirectory) throws Exception {
this.dbms = setupDbms(workingDirectory.toPath());
this.defaultDb = dbms.database("neo4j");

Class<?>[] clazzArray = new Class<?>[0];
registerProcedures(procedures().toArray(clazzArray));
registerFunctions(functions().toArray(clazzArray));
for (var function : aggregationFunctions()) {
registerAggregationFunction(function);
GraphDatabaseService defaultDb = dbms.database("neo4j");
registerProcedures(defaultDb, procedures().toArray(clazzArray));
registerFunctions(defaultDb, functions().toArray(clazzArray));

for (CompatUserAggregationFunction func : aggregationFunctions()) {
GraphDatabaseApiProxy.register(defaultDb, Neo4jProxy.callableUserAggregationFunction(func));
}



var treeProcessor = new QueryCollectingTreeProcessor();
var includeProcessor = new PartialsIncludeProcessor();

Expand Down Expand Up @@ -93,6 +117,22 @@ void setUp(@TempDir File workingDirectory) throws Exception {
}
}

@AfterEach
void tearDownDbms() {
dbms.shutdown();
}

protected DatabaseManagementService setupDbms(Path workingDirectory) {
var builder = new TestDatabaseManagementServiceBuilder(Neo4jLayout.of(workingDirectory));
configureDbms(builder);
return builder.build();
}

protected void configureDbms(TestDatabaseManagementServiceBuilder builder) {
builder.noOpSystemGraphInitializer();
builder.setConfig(Settings.procedureUnrestricted(), singletonList("gds.*"));
}

private List<File> adocFiles() {
return adocPaths()
.stream()
Expand Down Expand Up @@ -127,10 +167,9 @@ private void beforeEachTest() {
private void runDocQuery(DocQuery docQuery) {
try {
if (docQuery.runAsOperator()) {
String operatorHandle = docQuery.operator();
super.runQuery(operatorHandle, docQuery.query());
QueryRunner.runQuery(dbms.database(docQuery.database()), docQuery.operator(), docQuery.query(), Map.of());
} else {
super.runQuery(docQuery.query());
QueryRunner.runQuery(dbms.database(docQuery.database()), docQuery.query());
}
} catch (Exception e) {
throw new RuntimeException("Failed to run query: " + docQuery.query(), e);
Expand All @@ -152,11 +191,20 @@ private DynamicTest createDynamicTest(QueryExampleGroup queryExampleGroup) {
}

private void runQueryExampleWithResultConsumer(QueryExample queryExample, Consumer<Result> check) {
if (!queryExample.runAsOperator()) {
super.runQueryWithResultConsumer(queryExample.query(), check);
if (queryExample.runAsOperator()) {
QueryRunner.runQueryWithResultConsumer(
dbms.database(queryExample.database()),
queryExample.operator(),
queryExample.query(),
Map.of(),
check
);
} else {
String operatorHandle = queryExample.operator();
super.runQueryWithResultConsumer(operatorHandle, queryExample.query(), check);
QueryRunner.runQueryWithResultConsumer(
dbms.database(queryExample.database()),
queryExample.query(),
Map.of(),
check);
}
}

Expand Down Expand Up @@ -207,15 +255,15 @@ private List<List<String>> reducePrecisionOfDoubles(Collection<List<String>> res
return resultsFromDoc
.stream()
.map(list -> list.stream().map(string -> {
try {
if (string.startsWith("[")) {
return formatListOfNumbers(string);
try {
if (string.startsWith("[")) {
return formatListOfNumbers(string);
}
return DocumentationTestToolsConstants.FLOAT_FORMAT.format(Double.parseDouble(string));
} catch (NumberFormatException e) {
return string;
}
return DocumentationTestToolsConstants.FLOAT_FORMAT.format(Double.parseDouble(string));
} catch (NumberFormatException e) {
return string;
}
}).collect(Collectors.toList())
}).collect(Collectors.toList())
)
.collect(Collectors.toList());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
import static org.neo4j.gds.doc.DocumentationTestToolsConstants.ROLE_SELECTOR;
import static org.neo4j.gds.doc.DocumentationTestToolsConstants.SETUP_QUERY_ROLE;
import static org.neo4j.gds.doc.DocumentationTestToolsConstants.TABLE_CONTEXT;
import static org.neo4j.gds.doc.DocumentationTestToolsConstants.TEST_DATABASE_ATTRIBUTE;
import static org.neo4j.gds.doc.DocumentationTestToolsConstants.TEST_GROUP_ATTRIBUTE;
import static org.neo4j.gds.doc.DocumentationTestToolsConstants.TEST_OPERATOR_ATTRIBUTE;
import static org.neo4j.gds.doc.DocumentationTestToolsConstants.TEST_TYPE_NO_RESULT;
Expand Down Expand Up @@ -163,6 +164,10 @@ private QueryExample convertToQueryExample(StructuralNode queryExampleNode) {
queryExampleBuilder.operator(queryExampleNode.getAttribute(TEST_OPERATOR_ATTRIBUTE).toString());
}

if (queryExampleNode.hasAttribute(TEST_DATABASE_ATTRIBUTE)) {
queryExampleBuilder.database(queryExampleNode.getAttribute(TEST_DATABASE_ATTRIBUTE).toString());
}

if (Boolean.parseBoolean(queryExampleNode.getAttribute(TEST_TYPE_NO_RESULT, false).toString())) {
queryExampleBuilder.assertResults(false);
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
package org.neo4j.gds.doc.syntax;

import org.immutables.value.Value;
import org.neo4j.configuration.GraphDatabaseSettings;
import org.neo4j.gds.annotation.ValueClass;

/**
Expand All @@ -41,6 +42,9 @@ default String operator() {
return DEFAULT_OPERATOR;
}

@Value.Default
default String database() { return GraphDatabaseSettings.DEFAULT_DATABASE_NAME;}

static ImmutableDocQuery.Builder builder() {
return ImmutableDocQuery.builder();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,10 @@ public enum SyntaxMode {
SYS_INFO("debug-sysinfo-syntax", false),
WRITE_NODE_LABEL("include-with-write-node-label", false),
MUTATE_NODE_LABEL("include-with-mutate-node-label", false),
GRAPH_GENERATE("include-with-graph-generate"),;

GRAPH_GENERATE("include-with-graph-generate"),
DB_CREATE("create-ephemeral-db-syntax", false),
DB_DROP("drop-ephemeral-db-syntax", false);

private final String mode;
public final boolean hasParameters;
public final CustomProcedure.Namespace namespace;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,22 @@ void loadsQueryExamplesCorrectly() {
);
}

@Test
void parseDatabase() {
processor = new QueryCollectingTreeProcessor();
asciidoctor.javaExtensionRegistry().treeprocessor(processor);

var file = ResourceUtil.path("query-collecting-tree-processor-test_database.adoc").toFile();
assertThat(file).exists().canRead();

asciidoctor.loadFile(file, Collections.emptyMap());

assertThat(processor.getQueryExampleGroups()).hasSize(1);
assertThat(processor.getQueryExampleGroups().get(0).queryExamples()).containsExactly(
QueryExample.builder().query("SHOW DATABASES").database("system").assertResults(false).build()
);
}

@Test
void parseMultipleDocuments() {
var file = ResourceUtil.path("query-collecting-tree-processor-test_part2.adoc").toFile();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
[role=query-example, database=system, no-result=true]
--
.This is a test code block
[source, cypher, role=noplay]
----
SHOW DATABASES
----
--
Original file line number Diff line number Diff line change
Expand Up @@ -21,24 +21,27 @@

import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.io.TempDir;
import org.neo4j.gds.api.schema.GraphSchema;
import org.neo4j.gds.compat.GraphDatabaseApiProxy;
import org.neo4j.gds.core.model.Model;
import org.neo4j.gds.core.model.ModelCatalog;
import org.neo4j.gds.core.model.TestCustomInfo;
import org.neo4j.gds.extension.Inject;
import org.neo4j.gds.extension.Neo4jModelCatalogExtension;
import org.neo4j.gds.model.catalog.TestTrainConfig;

import java.io.File;
import java.util.Map;

@Neo4jModelCatalogExtension
public abstract class ModelCatalogDocTestBase extends SingleFileDocTestBase {

@Inject
ModelCatalog modelCatalog;

@BeforeEach
void loadModel() {
@Override
void setUp(@TempDir File workingDirectory) throws Exception {
super.setUp(workingDirectory);
this.modelCatalog = GraphDatabaseApiProxy.resolveDependency(defaultDb, ModelCatalog.class);

var exampleModel1 = Model.of(
"example-model-type",
GraphSchema.empty(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,24 +20,19 @@
package org.neo4j.gds.doc;

import org.neo4j.gds.catalog.GraphProjectProc;
import org.neo4j.gds.compat.GraphDatabaseApiProxy;
import org.neo4j.gds.core.loading.GraphStoreCatalog;
import org.neo4j.gds.core.model.ModelCatalog;
import org.neo4j.gds.degree.DegreeCentralityMutateProc;
import org.neo4j.gds.embeddings.graphsage.GraphSageMutateProc;
import org.neo4j.gds.embeddings.graphsage.GraphSageStreamProc;
import org.neo4j.gds.embeddings.graphsage.GraphSageTrainProc;
import org.neo4j.gds.embeddings.graphsage.GraphSageWriteProc;
import org.neo4j.gds.extension.Inject;
import org.neo4j.gds.extension.Neo4jModelCatalogExtension;

import java.util.List;

@Neo4jModelCatalogExtension
class GraphSageDocTest extends SingleFileDocTestBase {

@Inject
ModelCatalog modelCatalog;

@Override
protected List<Class<?>> procedures() {
return List.of(
Expand All @@ -57,6 +52,7 @@ protected String adocFile() {

@Override
protected Runnable cleanup() {
var modelCatalog = GraphDatabaseApiProxy.resolveDependency(defaultDb, ModelCatalog.class);
return () -> {
GraphStoreCatalog.removeAllLoadedGraphs();
modelCatalog.removeAllLoadedModels();
Expand Down
2 changes: 1 addition & 1 deletion doc-test/src/test/java/org/neo4j/gds/doc/WccDocTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ boolean setupNeo4jGraphPerTest() {
Runnable cleanup() {
return () -> {
GraphStoreCatalog.removeAllLoadedGraphs();
db.executeTransactionally("MATCH (n) DETACH DELETE n");
defaultDb.executeTransactionally("MATCH (n) DETACH DELETE n");
};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,20 +39,23 @@ include::partial$/management-ops/ephemeral-db/create-example.adoc[]
=== Read query

We can now run a `MATCH` Cypher query on the in-memory database and check the results:

[source, cypher, role=noplay]
[role=query-example]
--
[source, cypher, role=noplay, database=gdsdb]
----
MATCH (n:Person)-[:KNOWS]->(m:Person) RETURN n.age AS age1, m.age AS age2
ORDER BY age1, age2
----

.Results
[opts="header"]
|===
| age1 | age2
| 23 | 31
| 23 | 42
| 42 | 23
| 23 | 31
|===
--

The returned properties correctly match those from the original graph.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ include::partial$/management-ops/ephemeral-db/create-example.adoc[]

As an example of updating the graph, we add a `level` node property only to some nodes that match a given condition:

[role=query-example]
--
[source, cypher, role=noplay]
----
MATCH (n:Person)
Expand All @@ -40,9 +42,12 @@ RETURN n.age, n.level
| n.age | n.level
| 42 | 3
|===
--

Then, we run a Cypher query to check that the new property has actually been added:

[role=query-example]
--
[source, cypher, role=noplay]
----
MATCH (n:Person)
Expand All @@ -56,6 +61,7 @@ RETURN n.age, n.level
| n.age | n.level
| 42 | 3
|===
--

[[drop-ephemeral-db]]
=== Cleanup
Expand Down
Loading

0 comments on commit 1101b4c

Please sign in to comment.