add specification test support module

This commit is contained in:
Jonathan Shook 2022-06-21 20:13:19 -05:00
parent ecabd71048
commit 334bbd0379
31 changed files with 1833 additions and 372 deletions

View File

@ -1,3 +1,19 @@
<!--
~ Copyright (c) 2022 nosqlbench
~
~ Licensed 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.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
@ -26,6 +42,12 @@
<version>4.17.15-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>io.nosqlbench</groupId>
<artifactId>nb-spectest</artifactId>
<version>4.17.15-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>io.nosqlbench</groupId>
<artifactId>drivers-api</artifactId>

View File

@ -16,368 +16,26 @@
package io.nosqlbench.engine.api.activityconfig.rawyaml;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonElement;
import com.google.gson.JsonParser;
import com.google.gson.reflect.TypeToken;
import com.vladsch.flexmark.ast.FencedCodeBlock;
import com.vladsch.flexmark.ext.yaml.front.matter.YamlFrontMatterExtension;
import com.vladsch.flexmark.parser.Parser;
import com.vladsch.flexmark.util.ast.Document;
import com.vladsch.flexmark.util.ast.Node;
import io.nosqlbench.engine.api.activityconfig.StatementsLoader;
import io.nosqlbench.engine.api.activityconfig.yaml.OpTemplate;
import io.nosqlbench.engine.api.activityconfig.yaml.StmtsDocList;
import io.nosqlbench.nb.api.content.NBIO;
import io.nosqlbench.nb.spectest.core.SpecTest;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.junit.jupiter.api.Test;
import java.io.IOException;
import java.lang.reflect.Type;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import static org.assertj.core.api.Assertions.assertThat;
public class UniformWorkloadSpecificationTest {
private final static Logger logger = LogManager.getLogger(UniformWorkloadSpecificationTest.class);
private static final Parser parser = Parser.builder().extensions(List.of(YamlFrontMatterExtension.create())).build();
private static final Gson gson = new GsonBuilder().setPrettyPrinting().create();
@Test
public void testTemplatedWorkloads() {
testSpecPath(
NBIO.fs().prefix("target/classes/workload_definition/")
.name("templated_workloads.md")
.one().asPath()
);
}
@Test
public void testTemplatedOperations() {
testSpecPath(
NBIO.fs().prefix("target/classes/workload_definition/")
.name("templated_operations.md")
.one().asPath()
);
}
@Test
public void testTemplatedOperationVariations() {
testSpecPath(
NBIO.fs().prefix("target/classes/workload_definition/")
.name("templated_operation_variations.md")
.one().asPath()
);
}
@Test
public void testTemplateVariables() {
testSpecPath(
NBIO.fs().prefix("target/classes/workload_definition/")
.name("template_variables.md")
.one().asPath()
);
}
@Test
public void testCommandAPI() {
testSpecPath(
NBIO.fs().prefix("target/classes/workload_definition/")
.name("command_api.md")
.one().asPath()
);
}
public String summarize(Node node) {
StringBuilder sb = new StringBuilder();
while (node != null) {
sb.append("-> ").append(node.getClass().getSimpleName()).append("\n");
sb.append(node.getChars()).append("\n");
node = node.getNext();
}
return sb.toString();
}
public String summarize(List<Node> nodes) {
StringBuilder sb = new StringBuilder();
for (Node node : nodes) {
sb.append(node.getClass().getSimpleName()).append("\n");
sb.append(node.getChars()).append("\n");
}
return sb.toString();
}
private void testSpecPath(Path specPath) {
LinkedList<TestSet> tests = new LinkedList<>();
Pattern emphasis = Pattern.compile("\\*(.*?)\\*\n");
Class<?> fcbclass = FencedCodeBlock.class;
NodePredicates p = new NodePredicates(emphasis, fcbclass, emphasis, fcbclass, emphasis, fcbclass);
HeadingScanner headings = new HeadingScanner(" > ");
List<TestBlock> testblocks = new ArrayList<>();
headings.update(specPath);
String input = null;
try {
input = Files.readString(specPath);
} catch (IOException e) {
throw new RuntimeException(e);
}
Document parsed = parser.parse(input);
Node node = parsed.getFirstChild();
// summarize(node,System.out);
int index = 0;
while (node != null) {
headings.update(node);
if (p.test(node)) {
List<Node> found = p.get();
// System.out.println(summarize(found));
testblocks.add(new TestBlock(
new TestSet(headings, found.get(0), found.get(1), specPath),
new TestSet(headings, found.get(2), found.get(3), specPath),
new TestSet(headings, found.get(4), found.get(5), specPath)
));
node = found.get(found.size() - 1);
headings.index();
}
if (node != null) {
node = node.getNext();
}
}
for (TestBlock testblock : testblocks) {
runTest(testblock);
}
}
private void runTest(TestBlock testblock) {
if (testblock.size() == 3) {
String tuple = testblock.get(0).info.toString() + "->" + testblock.get(1).info + "->" + testblock.get(2).info;
tuple = tuple.replaceAll("[^-a-zA-Z0-9<> _]", "");
System.out.println(testblock.get(0).getDesc());
System.out.println(testblock.get(0).getLocationRef());
if (tuple.equals("yaml->json->ops")) {
testBracket_YamlJsonOps(testblock);
}
} else {
throw new RuntimeException("Test block sized " + testblock.size() + " unrecognized by test loader.");
}
}
/**
* Not thread-safe!
*/
private static class NodePredicates implements Predicate<Node>, Supplier<List<Node>> {
final List<Predicate<Node>> predicates;
final List<Node> found = new ArrayList<>();
public NodePredicates(Object... predicates) {
this.predicates = Arrays.stream(predicates).map(NodePredicate::new).collect(Collectors.toList());
}
@Override
public boolean test(Node node) {
found.clear();
for (Predicate<Node> predicate : predicates) {
if (node == null) {
return false;
}
if (!predicate.test(node)) {
return false;
}
found.add(node);
node = node.getNext();
}
return true;
}
@Override
public List<Node> get() {
return List.copyOf(found);
}
}
/**
* Not thread-safe!
*/
private static class NodePredicate implements Predicate<Node>, Supplier<Node> {
private final Predicate<Node> predicate;
private Node found = null;
public NodePredicate(Object o) {
this.predicate = resolvePredicate(o);
}
private Predicate<Node> resolvePredicate(Object object) {
if (object instanceof Predicate) {
return (Predicate<Node>) object;
} else if (object instanceof Class) {
return (n) -> object.equals(n.getClass());
} else if (object instanceof Pattern) {
return (n) -> ((Pattern) object).matcher(n.getChars()).matches();
} else if (object instanceof CharSequence) {
return (n) -> Pattern.compile(object.toString()).matcher(n.getChars()).matches();
} else {
throw new RuntimeException("no Node predicate for type " + object.getClass().getSimpleName());
}
}
@Override
public boolean test(Node node) {
this.found = null;
boolean isFound = predicate.test(node);
if (isFound) {
this.found = node;
}
return isFound;
}
@Override
public Node get() {
return this.found;
}
}
private final static Predicate<Node> FORMATS = new Predicate<Node>() {
@Override
public boolean test(Node node) {
int lookahead = 6;
List<Class<?>> types = new ArrayList<>(lookahead);
return false;
}
};
private void testBracket_YamlJsonOps(TestBlock block) {
validateYamlWithJson(
block.get(0).getDesc(),
block.get(0).text.toString(),
block.get(1).text.toString(),
block.get(1)
);
validateYamlWithOpsModel(
block.get(0).getDesc(),
block.get(0).text.toString(),
block.get(2).text.toString(),
block.get(2)
);
}
private void validateYamlWithOpsModel(String desc, String yaml, String json, TestSet testref) {
System.out.format("%-40s", "- checking yaml->ops");
JsonParser parser = new JsonParser();
try {
JsonElement elem = parser.parse(json);
if (elem.isJsonArray()) {
Type type = new TypeToken<List<Map<String, Object>>>() {
}.getType();
List<Map<String, Object>> expectedList = gson.fromJson(json, type);
StmtsDocList stmtsDocs = StatementsLoader.loadString(yaml, Map.of());
List<OpTemplate> stmts = stmtsDocs.getStmts();
List<Map<String, Object>> stmt_objs = stmts.stream().map(OpTemplate::asData).collect(Collectors.toList());
assertThat(stmt_objs).isEqualTo(expectedList);
}
System.out.println("OK");
} catch (Exception e) {
// System.out.println("Error while validating equivalence between the yaml and the rendered op context:");
// System.out.println("yaml:");
// System.out.println(yaml);
// System.out.println("ops:");
// System.out.println(json);
throw new RuntimeException(e);
}
}
/**
* Compare one or more raw yaml docs to JSON5 representation of the same.
* For clarity in the docs, a single object is allowed in the json5, in which case
* an error is thrown if the yaml side contains more or less than 1 element.
*
* @param desc A moniker describing the test
* @param yaml YAML describing a templated workload
* @param json JSON describing a templated workload
*/
private void validateYamlWithJson(String desc, String yaml, String json, TestSet testset) {
System.out.format("%-40s", "- checking yaml->json");
// StmtsDocList stmts = StatementsLoader.loadString(yaml);
JsonParser parser = new JsonParser();
try {
List<Map<String, Object>> docmaps = new RawYamlLoader().loadString(logger, yaml);
JsonElement elem = JsonParser.parseString(json);
if (elem.isJsonArray()) {
Type type = new TypeToken<List<Map<String, Object>>>() {
}.getType();
List<Map<String, Object>> expectedList = gson.fromJson(json, type);
assertThat(docmaps).isEqualTo(expectedList);
System.out.println("OK");
} else if (elem.isJsonObject()) {
Map<String, Object> expectedSingle = gson.fromJson(json, Map.class);
compareEach(expectedSingle, docmaps.get(0));
assertThat(docmaps.get(0)).isEqualTo(expectedSingle);
if (docmaps.size() != 1) {
throw new RuntimeException("comparator expected a single object, but found " + docmaps.size());
}
System.out.println("OK");
} else {
System.out.println("ERROR");
throw new RuntimeException("unknown type in comparator: " + json);
}
} catch (Exception e) {
StringBuilder sb = new StringBuilder();
sb.append("error while verifying model:\n");
sb.append(" path: ").append(testset.getPath().toString()).append("\n");
sb.append(" line: ").append(testset.getRefNode().getLineNumber()).append("\n");
logger.error(sb + ": " + e.getMessage(), e);
throw new RuntimeException(e);
}
}
private void compareEach(List<Map<String, Object>> expected, List<Map<String, Object>> docmaps) {
assertThat(docmaps).isEqualTo(expected);
}
private void compareEach(Map<String, Object> structure, Map<String, Object> stringObjectMap) {
assertThat(stringObjectMap).isEqualTo(structure);
SpecTest specTester = SpecTest.builder()
.path("target/classes/workload_definition/")
.matchNodes(
Pattern.compile("\\*(.*?)\\*\n?"), FencedCodeBlock.class, 0, 1, 0 ,1)
.validators(new YamlSpecValidator())
.build();
specTester.run();
}
}

View File

@ -0,0 +1,197 @@
/*
* Copyright (c) 2022 nosqlbench
*
* Licensed 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 io.nosqlbench.engine.api.activityconfig.rawyaml;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonElement;
import com.google.gson.JsonParser;
import com.google.gson.reflect.TypeToken;
import io.nosqlbench.engine.api.activityconfig.StatementsLoader;
import io.nosqlbench.engine.api.activityconfig.yaml.OpTemplate;
import io.nosqlbench.engine.api.activityconfig.yaml.StmtsDocList;
import io.nosqlbench.nb.spectest.core.STNodeAssembly;
import io.nosqlbench.nb.spectest.loaders.STDefaultLoader;
import io.nosqlbench.nb.spectest.testtypes.STNamedCodeTuples;
import io.nosqlbench.nb.spectest.testtypes.STNodeReference;
import io.nosqlbench.nb.spectest.types.STAssemblyValidator;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.lang.reflect.Type;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import static org.assertj.core.api.Assertions.assertThat;
/**
* <P>This validator looks at a {@link STNodeAssembly} as a sequence of
* 6 parsed nodes, interpreted as 3 2-tuples of name and content,
* where the three tuples represent:
* <OL>
* <LI>An example op template in YAML form as provided by a user</LI>
* <LI>An equivalent example op template in JSON form as provided by a user</LI>
* <LI>The normalized op template datastructure as provided to the driver developer,
* represented as a JSON schematic.</LI>
* </OL>
*
* <P>These are checked for validity by first checking the first and second for data structure
* equivalence, and then processing the first through the op template API and checking the
* result with the third part.</P>
*
* <P>This validator is meant be used with {@link STNodeAssembly}s which are found by the
* {@link STDefaultLoader} scanner type.</P>
*
*
* </P>
*/
public class YamlSpecValidator implements STAssemblyValidator {
private final static Logger logger = LogManager.getLogger(YamlSpecValidator.class);
private final static Gson gson = new GsonBuilder().setPrettyPrinting().create();
@Override
public void validate(STNodeAssembly assembly) {
runTest(assembly);
}
private void runTest(STNodeAssembly testblock) {
if (testblock.size() == 6) {
STNamedCodeTuples tuples = testblock.getAsNameAndCodeTuples();
String name = tuples.getTypeSignature();
System.out.println(testblock.get(0).getDesc());
System.out.println(testblock.get(0).getLocationRef());
if (tuples.getTypeSignature().equals("yaml->json->ops")) {
testBracket_YamlJsonOps(tuples);
}
} else {
throw new RuntimeException("Test block sized " + testblock.size() + " unrecognized by test loader.");
}
}
private void testBracket_YamlJsonOps(STNamedCodeTuples tuples) {
validateYamlWithJson(
tuples.get(0).getName(),
tuples.get(0).getData(),
tuples.get(1).getName(),
tuples.get(1)
);
validateYamlWithOpsModel(
tuples.get(1).getName(),
tuples.get(1).getData(),
tuples.get(2).getData(),
tuples.get(2)
);
}
private void validateYamlWithOpsModel(String desc, String yaml, String json, STNodeReference testref) {
System.out.format("%-40s", "- checking yaml->ops");
try {
JsonElement elem = JsonParser.parseString(json);
if (elem.isJsonArray()) {
Type type = new TypeToken<List<Map<String, Object>>>() {
}.getType();
List<Map<String, Object>> expectedList = gson.fromJson(json, type);
StmtsDocList stmtsDocs = StatementsLoader.loadString(yaml, Map.of());
List<OpTemplate> stmts = stmtsDocs.getStmts();
List<Map<String, Object>> stmt_objs = stmts.stream().map(OpTemplate::asData).collect(Collectors.toList());
assertThat(stmt_objs).isEqualTo(expectedList);
}
System.out.println("OK");
} catch (Exception e) {
// System.out.println("Error while validating equivalence between the yaml and the rendered op context:");
// System.out.println("yaml:");
// System.out.println(yaml);
// System.out.println("ops:");
// System.out.println(json);
throw new RuntimeException(e);
}
}
/**
* Compare one or more raw yaml docs to JSON5 representation of the same.
* For clarity in the docs, a single object is allowed in the json5, in which case
* an error is thrown if the yaml side contains more or less than 1 element.
*
* @param desc A moniker describing the test
* @param yaml YAML describing a templated workload
* @param json JSON describing a templated workload
*/
private void validateYamlWithJson(String desc, String yaml, String json, STNodeReference testset) {
System.out.format("%-40s", "- checking yaml->json");
// StmtsDocList stmts = StatementsLoader.loadString(yaml);
try {
List<Map<String, Object>> docmaps = new RawYamlLoader().loadString(logger, yaml);
JsonElement elem = JsonParser.parseString(json);
if (elem.isJsonArray()) {
Type type = new TypeToken<List<Map<String, Object>>>() {
}.getType();
List<Map<String, Object>> expectedList = gson.fromJson(json, type);
assertThat(docmaps).isEqualTo(expectedList);
System.out.println("OK");
} else if (elem.isJsonObject()) {
Map<String, Object> expectedSingle = gson.fromJson(json, Map.class);
compareEach(expectedSingle, docmaps.get(0));
assertThat(docmaps.get(0)).isEqualTo(expectedSingle);
if (docmaps.size() != 1) {
throw new RuntimeException("comparator expected a single object, but found " + docmaps.size());
}
System.out.println("OK");
} else {
System.out.println("ERROR");
throw new RuntimeException("unknown type in comparator: " + json);
}
} catch (Exception e) {
StringBuilder sb = new StringBuilder();
sb.append("error while verifying model:\n");
sb.append(" path: ").append(testset.getPath().toString()).append("\n");
sb.append(" line: ").append(testset.getLineNumber()).append("\n");
logger.error(sb + ": " + e.getMessage(), e);
throw new RuntimeException(e);
}
}
private void compareEach(List<Map<String, Object>> expected, List<Map<String, Object>> docmaps) {
assertThat(docmaps).isEqualTo(expected);
}
private void compareEach(Map<String, Object> structure, Map<String, Object> stringObjectMap) {
assertThat(stringObjectMap).isEqualTo(structure);
}
}

80
nb-spectest/pom.xml Normal file
View File

@ -0,0 +1,80 @@
<!--
~ Copyright (c) 2022 nosqlbench
~
~ Licensed 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.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>mvn-defaults</artifactId>
<groupId>io.nosqlbench</groupId>
<version>4.17.15-SNAPSHOT</version>
<relativePath>../mvn-defaults</relativePath>
</parent>
<artifactId>nb-spectest</artifactId>
<packaging>jar</packaging>
<name>${project.artifactId}</name>
<description>
This module provides the ability to run tests based on tests as specification,
using markdown as a wrapper format. This allows for a type of literate exposition
of various tests in a form that can be used as markdown documentation.
With this, tests, examples, specifications, and documentation can all be one
and the same.
</description>
<dependencies>
<dependency>
<groupId>com.vladsch.flexmark</groupId>
<artifactId>flexmark-ext-yaml-front-matter</artifactId>
<version>0.62.2</version>
</dependency>
<dependency>
<groupId>com.vladsch.flexmark</groupId>
<artifactId>flexmark-html2md-converter</artifactId>
<version>0.62.2</version>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
</dependency>
<!-- perf testing -->
<dependency>
<groupId>org.openjdk.jmh</groupId>
<artifactId>jmh-core</artifactId>
</dependency>
<dependency>
<groupId>org.openjdk.jmh</groupId>
<artifactId>jmh-generator-annprocess</artifactId>
</dependency>
</dependencies>
<build>
<resources>
<resource>
<directory>src/main/resources</directory>
<excludes>
<exclude>docs-for-testing-only/**</exclude>
</excludes>
<includes>
<include>**</include>
</includes>
</resource>
</resources>
</build>
</project>

View File

@ -0,0 +1,53 @@
/*
* Copyright (c) 2022 nosqlbench
*
* Licensed 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 io.nosqlbench.nb.spectest.core;
import io.nosqlbench.nb.spectest.testtypes.STNodeReference;
import java.nio.file.Path;
public class STNameAndCodeTuple implements STNodeReference {
private final STNode nameNode;
private final STNode dataNode;
public STNameAndCodeTuple(STNode nameNode, STNode dataNode) {
this.nameNode = nameNode;
this.dataNode = dataNode;
}
public String getDesc() {
return nameNode.getDesc();
}
public String getName() {
return nameNode.text.toString();
}
public String getData() {
return dataNode.text.toString();
}
@Override
public Path getPath() {
return dataNode.getPath();
}
@Override
public int getLineNumber() {
return dataNode.getLine();
}
}

View File

@ -14,15 +14,23 @@
* limitations under the License.
*/
package io.nosqlbench.engine.api.activityconfig.rawyaml;
package io.nosqlbench.nb.spectest.core;
import com.vladsch.flexmark.util.ast.Node;
import java.nio.file.Path;
import java.util.Locale;
import java.util.Objects;
import java.util.function.Supplier;
final class TestSet {
/**
* A {@link STNode} is a set of metadata that describes a test target from a
* test specification file, in terms of {@link Node} sequences and context.
* It contains naming and locating information as well as the document nodes containing
* the target element.
*/
public final class STNode {
private final String description;
private final Path path;
private final int line;
@ -30,9 +38,8 @@ final class TestSet {
public CharSequence info;
public CharSequence text;
public TestSet(Supplier<CharSequence> desc, Node infoNode, Node dataNode, Path path) {
public STNode(Supplier<CharSequence> desc, Node dataNode, Path path) {
this.description = desc.get().toString();
this.info = infoNode.getChars();
this.text = dataNode.getFirstChild().getChars();
this.line = dataNode.getFirstChild().getLineNumber();
this.path = path;
@ -58,7 +65,7 @@ final class TestSet {
/**
* Provide the logical path of the file being examined in this test set.
* If the system properties indicate that the test is being run from within intellij,
* the path will be relatized from the next module level up to allow for hot linking
* the path will be relativized from the next module level up to allow for hot linking
* directly to files.
* @return A useful relative path to the file being tested
*/
@ -77,4 +84,30 @@ final class TestSet {
public String toString() {
return this.getDesc();
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
STNode that = (STNode) o;
if (line != that.line) return false;
if (!Objects.equals(description, that.description)) return false;
if (!Objects.equals(path, that.path)) return false;
if (!Objects.equals(refnode, that.refnode)) return false;
if (!Objects.equals(info, that.info)) return false;
return Objects.equals(text, that.text);
}
@Override
public int hashCode() {
int result = description != null ? description.hashCode() : 0;
result = 31 * result + (path != null ? path.hashCode() : 0);
result = 31 * result + line;
result = 31 * result + (refnode != null ? refnode.hashCode() : 0);
result = 31 * result + (info != null ? info.hashCode() : 0);
result = 31 * result + (text != null ? text.hashCode() : 0);
return result;
}
}

View File

@ -0,0 +1,99 @@
/*
* Copyright (c) 2022 nosqlbench
*
* Licensed 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 io.nosqlbench.nb.spectest.core;
import com.vladsch.flexmark.util.ast.Node;
import io.nosqlbench.nb.spectest.testtypes.STNamedCodeTuples;
import java.nio.file.Path;
import java.security.InvalidParameterException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* <P>A {@link STNodeAssembly} is a sequence of {@link STNode}s. This is meant to
* group sets of test data together into a single cohesive specification which
* contains its own set of documentation, examples, and full-content validation.
* As such, the markdown which provides the specification should be usable directly
* as detailed user-facing documentation.</P>
*
* <P>The method of discovery, assembly, and validation of a {@link STNodeAssembly} is
* determined by the testing harness assembled around it. In any case, the type of
* presentation structure and validation logic that accompanies a given assembly
* should be self-evident, and documented clearly within the surrounding
* markdown.</P>
*/
public class STNodeAssembly extends ArrayList<STNode> {
public STNodeAssembly(STNode... testsets) {
this.addAll(Arrays.asList(testsets));
}
@Override
public String toString() {
return (size()>0 ? get(0).toString() : "NONE");
}
public STNamedCodeTuples getAsNameAndCodeTuples() {
if ((size()%2)==0) {
List<STNameAndCodeTuple> tuples = new ArrayList<>();
for (int i = 0; i < size(); i+=2) {
tuples.add(new STNameAndCodeTuple(get(i),get(i+1)));
}
return new STNamedCodeTuples(tuples);
} else {
throw new InvalidParameterException("Impossible with an odd number of elements.");
}
}
public String getDescription(int index) {
assertRange(index);
return get(index).getDesc();
}
public String getDescription() {
if (size()==0) {
return "";
}
return get(0).getDesc();
}
public String getAsText(int index) {
assertRange(index);
return get(index).text.toString();
}
public Path getPath() {
if (size()==0) {
return null;
}
return get(0).getPath();
}
public Node getRefNode(int index) {
assertRange(index);
return get(index).getRefNode();
}
private void assertRange(int index) {
if (size()-1<index) {
throw new InvalidParameterException("index " + index + " was out of bounds. Your pattern matching and access patterns to this data are not in sync.");
}
}
}

View File

@ -0,0 +1,123 @@
/*
* Copyright (c) 2022 nosqlbench
*
* Licensed 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 io.nosqlbench.nb.spectest.core;
import com.vladsch.flexmark.ext.yaml.front.matter.YamlFrontMatterExtension;
import com.vladsch.flexmark.parser.Parser;
import io.nosqlbench.nb.spectest.loaders.STFileScanner;
import io.nosqlbench.nb.spectest.types.STAssemblyValidator;
import io.nosqlbench.nb.spectest.types.STBuilderFacets;
import io.nosqlbench.nb.spectest.types.STPathLoader;
import java.nio.file.Path;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
/**
* This is the entry point into a specification test. By default, all files
* within the provided path are checked for the scanner patterns,
* and all validators are run. This means:
*
* <OL>
* <LI>All provided paths which are markdown files will be scanned.</LI>
* <LI>All provided paths which are directories will be recursively scanned for contained markdown files.</LI>
* <LI>Every included markdown file will be scanned for matching element sequences.</LI>
* <LI>Every matching element sequence will be validated.</LI>
* </OL>
*
* <p>The details of each of these steps should be documented in the specific classes which implement
* the behavior. At a high level:
*
* Key concepts are:
* <UL>
* <LI>Specification Files - These are the markdown files and included sets of specification tuples.
* Specification tuples are sequences of content elements which are recognizable by a scanner
* as going together to form a multi-part specification and test.</LI>
* <LI>Scanners - A scanner is a pattern matcher specification, in the form of a set of predicate objects.
* These are tested against sequences of elements that are found within specification files. Each
* time a scanner matches a sequence of elements, a specification template is extracted and submitted
* to a set of validators.</LI>
* <LI>Specification Template - This is merely a set of content elements which represents a specific
* specification or example. When a specification template is found, it is extracted with some context
* metadata that makes it easy to identify and report (like the heading), as well as the content needed
* for a specification validator to confirm whether or not it passes a test for validity.</LI>
* <LI>Validators - A validator knows how to assert the validity of a specification template. It is
* essentially a set of test assertions against a specification template. There are default validators
* which do different types of validation based on the content type.</LI>
* </UL>
*
* Effectively, all specifications are described prosaically within the markdown, while the actual specifications
* which are meant to be validated are captured within fenced code sections. The type of fenced code section
* determines how the inner content is interpreted and validated.
*
* </p>
*/
public class SpecTest implements Runnable {
private static final Parser parser = Parser.builder().extensions(List.of(YamlFrontMatterExtension.create())).build();
private final List<Path> paths;
private final List<STPathLoader> pathLoaders;
private final List<STAssemblyValidator> validators;
private SpecTest(List<Path> paths, List<STPathLoader> pathLoaders, List<STAssemblyValidator> validators) {
this.paths = paths;
this.pathLoaders = pathLoaders;
this.validators = validators;
}
@Override
public void run() {
Set<STNodeAssembly> testables = new LinkedHashSet<>();
for (Path path : paths) {
List<Path> matchingPaths = STFileScanner.findMatching(".*\\.md", paths.toArray(new Path[0]));
for (Path matchingPath : matchingPaths) {
// STNodeScanner scanner = new STDefaultNodeScanner();
for (STPathLoader pathLoader : pathLoaders) {
List<STNodeAssembly> testableAssemblies = pathLoader.apply(matchingPath);
for (STNodeAssembly assembly : testableAssemblies) {
if (testables.contains(assembly)) {
throw new RuntimeException("Found duplicate testable assembly in path set:\n" +
"assembly: " + assembly.toString());
}
testables.add(assembly);
}
}
}
}
for (STNodeAssembly assembly : testables) {
for (STAssemblyValidator validator : validators) {
validator.validate(assembly);
}
}
}
public static STBuilderFacets.WantsPaths builder() {
return new SpecTest.Builder();
}
private static class Builder extends SpecTestBuilder {
@Override
public SpecTest build() {
return new SpecTest(paths,scanners,validators);
}
}
}

View File

@ -0,0 +1,64 @@
/*
* Copyright (c) 2022 nosqlbench
*
* Licensed 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 io.nosqlbench.nb.spectest.core;
import io.nosqlbench.nb.spectest.loaders.STDefaultLoader;
import io.nosqlbench.nb.spectest.loaders.STNodePredicate;
import io.nosqlbench.nb.spectest.types.STAssemblyValidator;
import io.nosqlbench.nb.spectest.types.STBuilderFacets;
import io.nosqlbench.nb.spectest.types.STPathLoader;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public abstract class SpecTestBuilder implements STBuilderFacets.All {
protected List<Path> paths = new ArrayList<>();
protected List<STPathLoader> scanners = new ArrayList<>();
protected List<STAssemblyValidator> validators = new ArrayList<>();
@Override
public STBuilderFacets.WantsPathsOrScannersOrValidators paths(Path... paths) {
this.paths.addAll(Arrays.asList(paths));
return this;
}
@Override
public STBuilderFacets.WantsValidatorsOrDone scanners(STPathLoader... scanners) {
this.scanners.addAll(Arrays.asList(scanners));
return this;
}
@Override
public STBuilderFacets.WantsValidatorsOrDone scanners(STNodePredicate predicate) {
return this.matchNodes(predicate);
}
@Override
public STBuilderFacets.WantsValidatorsOrDone matchNodes(Object... predicates) {
return this.scanners(new STDefaultLoader(predicates));
}
@Override
public STBuilderFacets.Done validators(STAssemblyValidator... validators) {
this.validators.addAll(Arrays.asList(validators));
return this;
}
}

View File

@ -0,0 +1,96 @@
/*
* Copyright (c) 2022 nosqlbench
*
* Licensed 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 io.nosqlbench.nb.spectest.loaders;
import com.vladsch.flexmark.ext.yaml.front.matter.YamlFrontMatterExtension;
import com.vladsch.flexmark.parser.Parser;
import io.nosqlbench.nb.spectest.core.STNodeAssembly;
import io.nosqlbench.nb.spectest.types.STAssemblyValidator;
import io.nosqlbench.nb.spectest.types.STNodeLoader;
import io.nosqlbench.nb.spectest.types.STPathLoader;
import java.nio.file.Path;
import java.security.InvalidParameterException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* <P>This scanner looks for testable specifications which are matched as a sequence of
* 3 2-tuples, described in {@link YamlSpecValidator}. The required markdown structure for
* this looks like:
* <PRE>{@code
* ## call form with defaults
*
* *yaml:*
* ```yaml
* name: TEMPLATE(myname,thedefault)
* ```
*
* *json:*
* ```json5
* {
* "name": "thedefault"
* }
* ```
*
* *ops:*
* ```json5
* []
* ```
* }</PRE>
* </P>
*
* <P>Specifically, the emphasis and colon paragraph blocks indicate the naming of the elements, and the
* fenced code sections represent the content. The name elements are not matched for the name specifically,
* although the {@link STAssemblyValidator} which consumes these {@link STNodeAssembly}s will interpret them
* as described above.
* </P>
*
*/
public class STDefaultLoader implements STPathLoader {
private final STNodePredicates predicates;
private static final Parser parser = Parser.builder().extensions(List.of(YamlFrontMatterExtension.create())).build();
public STDefaultLoader(Object... predicates) {
if (predicates.length==0) {
throw new InvalidParameterException("An empty spec scanner is invalid.");
}
if ((predicates.length % 2) != 0) {
throw new InvalidParameterException("You can only provide predicates in sequences of 2-tuples, where" +
"each even index is a naming element and each odd index is the associated test content. " +
"But " + predicates.length + " were provided: " + Arrays.toString(predicates));
}
this.predicates = new STNodePredicates(predicates);
}
@Override
public List<STNodeAssembly> apply(Path specPath) {
List<STNodeAssembly> assemblies = new ArrayList<>();
List<Path> matchingPaths = STFileScanner.findMatching(specPath);
STNodeLoader nodeLoader = new STDefaultNodeLoader(predicates);
for (Path matchingPath : matchingPaths) {
List<STNodeAssembly> found = nodeLoader.apply(matchingPath, null);
assemblies.addAll(found);
}
return assemblies;
}
}

View File

@ -0,0 +1,95 @@
/*
* Copyright (c) 2022 nosqlbench
*
* Licensed 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 io.nosqlbench.nb.spectest.loaders;
import com.vladsch.flexmark.ext.yaml.front.matter.YamlFrontMatterExtension;
import com.vladsch.flexmark.parser.Parser;
import com.vladsch.flexmark.util.ast.Document;
import com.vladsch.flexmark.util.ast.Node;
import io.nosqlbench.nb.spectest.core.STNode;
import io.nosqlbench.nb.spectest.core.STNodeAssembly;
import io.nosqlbench.nb.spectest.types.STNodeLoader;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.InvalidParameterException;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
public class STDefaultNodeLoader implements STNodeLoader {
private final STNodePredicates predicates;
private final Parser parser = Parser.builder().extensions(List.of(YamlFrontMatterExtension.create())).build();
public STDefaultNodeLoader(Object... predicates) {
// if (predicates.length==0) {
// throw new InvalidParameterException("An empty spec scanner is invalid.");
// }
// if ((predicates.length % 2) != 0) {
// throw new InvalidParameterException("You can only provide predicates in sequences of 2-tuples, where" +
// "each even index is a naming element and each odd index is the associated test content. " +
// "But " + predicates.length + " were provided: " + Arrays.toString(predicates));
// }
this.predicates = new STNodePredicates(predicates);
}
@Override
public List<STNodeAssembly> apply(Path path, Node node) {
List<STNodeAssembly> assemblies = new ArrayList<>();
if (node==null) {
if (path==null) {
throw new InvalidParameterException("You must provide at least a path.");
}
try {
String input = Files.readString(path);
node = parser.parse(input);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
if (node instanceof Document d) {
node=d.getFirstChild();
}
STHeadingScanner headings = new STHeadingScanner(" > ");
headings.update(path); // null is allowed here
while (node != null) {
headings.update(node);
Optional<List<Node>> optionalStanza = predicates.apply(node);
if (optionalStanza.isPresent()) {
List<Node> found = optionalStanza.get();
List<STNode> stnodes = found.stream().map(n -> new STNode(headings, n, path)).toList();
STNodeAssembly testassy = new STNodeAssembly(stnodes.toArray(new STNode[0]));
node = found.get(found.size() - 1);
headings.index();
assemblies.add(testassy);
}
if (node != null) {
node = node.getNext();
}
}
return assemblies;
}
}

View File

@ -0,0 +1,59 @@
/*
* Copyright (c) 2022 nosqlbench
*
* Licensed 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 io.nosqlbench.nb.spectest.loaders;
import java.io.IOException;
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;
public class STFileScanner {
public static List<Path> findMatching(Path... paths) {
return findMatching(".*\\.md",paths);
}
public static List<Path> findMatching(String regex, Path... paths) {
List<Path> found = new ArrayList<>();
return findMatching(found, Pattern.compile(regex), paths);
}
private static List<Path> findMatching(List<Path> accumulator, Pattern regex, Path... paths) {
for (Path path : paths) {
if (Files.isDirectory(path)) {
DirectoryStream<Path> dirdata;
try {
dirdata = Files.newDirectoryStream(path);
} catch (IOException e) {
throw new RuntimeException("Error while scanning for specifications: " + e,e);
}
for (Path dirdatum : dirdata) {
findMatching(accumulator,regex,dirdatum);
}
} else if (Files.isReadable(path)) {
if (regex.matcher(path.getFileName().toString()).matches()) {
accumulator.add(path);
}
} else {
throw new RuntimeException("unreadable path: '" + path + "'");
}
}
return accumulator;
}
}

View File

@ -14,7 +14,7 @@
* limitations under the License.
*/
package io.nosqlbench.engine.api.activityconfig.rawyaml;
package io.nosqlbench.nb.spectest.loaders;
import com.vladsch.flexmark.ast.Heading;
@ -23,13 +23,13 @@ import java.util.LinkedList;
import java.util.ListIterator;
import java.util.function.Supplier;
public class HeadingScanner implements Supplier<CharSequence> {
public class STHeadingScanner implements Supplier<CharSequence> {
private final LinkedList<Heading> tnodes = new LinkedList<>();
private Path path;
private int index;
private final String delimiter;
public HeadingScanner(String delimiter) {
public STHeadingScanner(String delimiter) {
this.delimiter = delimiter;
}
@ -56,10 +56,11 @@ public class HeadingScanner implements Supplier<CharSequence> {
}
/**
* If this method is called, then the heading is presented as
* If this method is called, then the heading index is incremented to track
* enumerations (represented as numeric indices) of headings within a common flow of elements
* @return
*/
public HeadingScanner index() {
public STHeadingScanner index() {
index++;
return this;
}
@ -74,8 +75,9 @@ public class HeadingScanner implements Supplier<CharSequence> {
* @param object Any object which might be a Heading or Path
* @return this HeadingScanner for method chaining
*/
public HeadingScanner update(Object object) {
public STHeadingScanner update(Object object) {
if (object==null ) {
updatePath(null);
} else if (object instanceof Path) {
updatePath((Path)object);
} else if (object instanceof Heading) {
@ -114,4 +116,5 @@ public class HeadingScanner implements Supplier<CharSequence> {
public CharSequence get() {
return toString();
}
}

View File

@ -0,0 +1,125 @@
/*
* Copyright (c) 2022 nosqlbench
*
* Licensed 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 io.nosqlbench.nb.spectest.loaders;
import com.vladsch.flexmark.ast.Paragraph;
import com.vladsch.flexmark.util.ast.Node;
import com.vladsch.flexmark.util.sequence.BasedSequence;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* <P>Construct a sequence of {com.vladsch.flexmark.util.ast.Node} matchers using
* one of a few common predicate forms, inferred from the types of the predicate
* formats provided.</P>
*
* <P>The available forms are:
* <OL>
* <LI>A fully constructed {@link Predicate} of {@link Node}. When provided,
* this predicate is automatically checked against dummy data to ensure
* the generic signature is valid.</LI>
* <LI>A {@link Class} of any type of {@link Node}, which asserts that the
* node's class is equal to that specified. (No type hierarchy checks are allowed.)</LI>
* <LI>A fully compiled {@link Pattern}, which checks the textual content of
* the node for a match.</LI>
* <LI>A string, which is automatically promoted to a pattern as above, and checked
* in the same way. By default, patterns provided this way are automatically
* prefixed and suffixed with '.*' unless a regex character is found there. Also,
* all matches are created with Pattern.MULTILINE and Pattern.DOTALL.
* </LI>
* </OL>
* </P>
*/
public class STNodePredicate implements Predicate<Node>, Supplier<Node> {
private final Predicate<Node> predicate;
private Node found = null;
public STNodePredicate(Object o) {
this.predicate = resolvePredicate(o);
}
private Predicate<Node> resolvePredicate(Object object) {
if (object instanceof Predicate predicate) {
Paragraph paragraph = new Paragraph(BasedSequence.of("test paragraph"));
predicate.test(paragraph);
return predicate;
} else if (object instanceof Class c) {
return new ClassPredicate(c);
} else if (object instanceof Pattern p) {
return new PatternPredicate(p);
} else if (object instanceof CharSequence cs) {
return new PatternPredicate(cs.toString());
} else {
throw new RuntimeException("no Node predicate for type " + object.getClass().getSimpleName());
}
}
private final static class ClassPredicate implements Predicate<Node> {
private final Class<? extends Node> matchingClass;
public ClassPredicate(Class<? extends Node> matchingClass) {
this.matchingClass = matchingClass;
}
@Override
public boolean test(Node node) {
Class<? extends Node> classToMatch = node.getClass();
boolean matches = matchingClass.equals(classToMatch);
return matches;
}
}
private final static class PatternPredicate implements Predicate<Node> {
private final Pattern pattern;
private PatternPredicate(Pattern pattern) {
this.pattern = pattern;
}
private PatternPredicate(String pattern) {
String newPattern = (pattern.matches("^[a-zA-Z0-9]") ? ".*" : "") + pattern + (pattern.matches("[a-zA-Z0-9]$") ? ".*" : "");
Pattern compiled = Pattern.compile(newPattern, Pattern.MULTILINE | Pattern.DOTALL);
this.pattern = compiled;
}
@Override
public boolean test(Node node) {
BasedSequence chars = node.getChars();
Matcher matcher = pattern.matcher(chars);
boolean matches = matcher.matches();
return matches;
}
}
@Override
public boolean test(Node node) {
this.found = null;
boolean isFound = predicate.test(node);
if (isFound) {
this.found = node;
}
return isFound;
}
@Override
public Node get() {
return this.found;
}
}

View File

@ -0,0 +1,91 @@
/*
* Copyright (c) 2022 nosqlbench
*
* Licensed 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 io.nosqlbench.nb.spectest.loaders;
import com.vladsch.flexmark.util.ast.Node;
import java.security.InvalidParameterException;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.function.Function;
import java.util.function.Predicate;
/**
* <P>{@link STNodePredicates} is a convenient wrapper around {@link STNodePredicate}
* evaluation so that creating a {@link Node} scanner is a one-liner.
* Any of the supported types for {@link STNodePredicate} are valid, in addition
* to numbers which reference previous positions in the argument list (zero-indexed.)</P>
*
* <p>This allows for existing node matchers to be re-used by reference. For Example,
* a specification like <pre>{@code new NodePredicates("__\\w__",0,0)}</pre> represents
* a regular expression which would match "__a__" or "__b__" and then two more subsequent
* matches against similar content, for a total of 3 consecutive matching nodes.</p>
*/
public class STNodePredicates implements Function<Node, Optional<List<Node>>> {
final List<Predicate<Node>> predicates;
final List<Node> found = new ArrayList<>();
public STNodePredicates(STNodePredicates predicates) {
this.predicates = predicates.predicates;
}
public STNodePredicates(Object... predicateSpecs) {
this.predicates = new ArrayList<Predicate<Node>>(predicateSpecs.length);
for (int i = 0; i < predicateSpecs.length; i++) {
if (predicateSpecs[i] instanceof STNodePredicates pspecs) {
predicates.addAll(pspecs.predicates);
} else if (predicateSpecs[i] instanceof Number number) {
if (i > number.intValue()) {
predicates.add(predicates.get(number.intValue()));
} else {
throw new InvalidParameterException("predicate reference at " + i + " references invalid position at " + number.intValue());
}
} else {
predicates.add(new STNodePredicate(predicateSpecs[i]));
}
}
}
@Override
public Optional<List<Node>> apply(Node node) {
if (test(node)) {
return Optional.of(List.copyOf(found));
} else {
return Optional.empty();
}
}
private boolean test(Node node) {
found.clear();
for (Predicate<Node> predicate : predicates) {
if (node == null) {
return false;
}
if (!predicate.test(node)) {
return false;
}
found.add(node);
node = node.getNext();
}
return true;
}
}

View File

@ -0,0 +1,75 @@
/*
* Copyright (c) 2022 nosqlbench
*
* Licensed 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.
*/
/**
* <P>This package and module provides a set of modular testing tools which enable
* specifications, documentation, and tests to be one and the same. This is a form
* of literate programming, but more focused on providing a clearly documented
* and tested specification, including detailed examples and corner cases.</P>
*
* <H2>Motivation</H2>
* <P>There is a common problem in many OSS projects and code bases which has no
* simple and definitive solution so far: Many APIs, type systems, protocols, and other
* foundational design elements which are the bedrock of stable systems are themselves
* unstable in practice. While it is true that this has much to do with management
* style and project hygiene, and thus varies wildly from project to project, it is
* still true that there exists no generally accepted method of ensconcing those
* for users in a way that remains correct, provable, and current. In other words,
* identifying the foundational elements of a system which should be held up as
* key concepts and so reliable that they can be taken for granted is crucial to
* the longevity and utility of any non-trivial OSS project. This module and package
* are simply a support system to enable some degree of that within the NoSQLBench
* project.</P>
*
* <H2>Concepts</H2>
* <p>
* Specifications to be used with this system are expected to be woven into a set
* of user-appropriate markdown documentation (or other type of documentation system,
* if you have an need for another, jump in and help!). Any documentation form is
* allowed as long as there is some regular structure within the documentation that can be
* used to extract stanzas of specification. These stanzas will generally be a sequence
* of parsed nodes which fit a sequence pattern. During test phases, provided paths
* are scanned for matching markdown content, that content is scanned for specific
* node patterns, and those sequences are extracted into self-contained test definitions,
* known as {@link io.nosqlbench.nb.spectest.core.STNodeAssembly}. Since all of the wiring
* for these tests operates on the AST of a parsed markdown file, the
* {@link com.vladsch.flexmark.util.ast.Node} nomenclature is retained in all the class names.
* Those test definitions are then fed to the validators which are provided in test code.
* </p>
*
* <H2>Validation Methods</H2>
* <P>All of the validations are meant to check the correctness of a type of operation. This can be some
* thing as simple as loading a data structure into memory and verifying that it is equal to a known value. It is important
* to bear in mind that no specification for a system stands alone. The whole point of having the
* specification is to capture, at an implementable, testable, mechanical level of detail, how
* the system in question should behave. More specifically, the specification is meant to say what
* the system should do with inputs of any kind, and how you can know that it is doing that faithfully.
* As such, you should endeavor to capture these behaviors within your spec, such that the stanzas
* hold enough information to actually exercise parts of a system which can prove or disprove it's
* conformance to a specification. Any tests which do not do this are merely documenting a format and
* are of little use above and beyond documentation.</p>
*
* <H3>Fenced Data</H3>
* <p>Generally speaking, you will provide fenced code within your documentation that can be presented
* in a friendly format to readers of your documentation system. These fenced code sections are the
* easiest place to put data which needs to be verified. Within a stanza, adjacent fenced code sections
* can represent testable tuples, like (program, output), or (input, program, output). It is up
* to you to decide what the structure should be. Further, it is useful to look at the fenced code
* </p>
*
* <H3>Output Assertions</H3>
*/
package io.nosqlbench.nb.spectest;

View File

@ -0,0 +1,114 @@
/*
* Copyright (c) 2022 nosqlbench
*
* Licensed 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 io.nosqlbench.nb.spectest.testtypes;
import com.vladsch.flexmark.util.ast.Node;
import java.nio.file.Path;
import java.util.Locale;
import java.util.Objects;
import java.util.function.Supplier;
/**
* A {@link STNameCodeTuple} is a set of metadata that describes a test target from a
* test specification file, in terms of {@link Node} sequences and context.
* It contains naming and locating information as well as the document nodes containing
* the target element.
*/
public final class STNameCodeTuple {
private final String description;
private final Path path;
private final int line;
private final Node refnode;
public CharSequence info;
public CharSequence text;
public STNameCodeTuple(Supplier<CharSequence> desc, Node infoNode, Node dataNode, Path path) {
this.description = desc.get().toString();
this.info = infoNode.getChars();
this.text = dataNode.getFirstChild().getChars();
this.line = dataNode.getFirstChild().getLineNumber();
this.path = path;
this.refnode = dataNode;
}
public String getDesc() {
return description;
}
public Path getPath() {
return path;
}
public int getLine() {
return line;
}
public Node getRefNode() {
return refnode;
}
/**
* Provide the logical path of the file being examined in this test set.
* If the system properties indicate that the test is being run from within intellij,
* the path will be relativized from the next module level up to allow for hot linking
* directly to files.
* @return A useful relative path to the file being tested
*/
public String getLocationRef() {
boolean inij = System.getProperty("sun.java.command","").toLowerCase(Locale.ROOT).contains("intellij");
Path vcwd = Path.of(".").toAbsolutePath().normalize();
vcwd = inij ? vcwd.getParent().normalize() : vcwd;
Path relpath = vcwd.relativize(this.path.toAbsolutePath());
if (inij) {
relpath = Path.of(relpath.toString().replace("target/classes/","src/main/resources/"));
}
return "\t at (" + relpath + ":" + this.getLine() + ")";
}
@Override
public String toString() {
return this.getDesc();
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
STNameCodeTuple that = (STNameCodeTuple) o;
if (line != that.line) return false;
if (!Objects.equals(description, that.description)) return false;
if (!Objects.equals(path, that.path)) return false;
if (!Objects.equals(refnode, that.refnode)) return false;
if (!Objects.equals(info, that.info)) return false;
return Objects.equals(text, that.text);
}
@Override
public int hashCode() {
int result = description != null ? description.hashCode() : 0;
result = 31 * result + (path != null ? path.hashCode() : 0);
result = 31 * result + line;
result = 31 * result + (refnode != null ? refnode.hashCode() : 0);
result = 31 * result + (info != null ? info.hashCode() : 0);
result = 31 * result + (text != null ? text.hashCode() : 0);
return result;
}
}

View File

@ -0,0 +1,79 @@
/*
* Copyright (c) 2022 nosqlbench
*
* Licensed 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 io.nosqlbench.nb.spectest.testtypes;
import io.nosqlbench.nb.spectest.core.STNameAndCodeTuple;
import io.nosqlbench.nb.spectest.core.STNode;
import java.util.List;
/**
* This is a view over an {@link io.nosqlbench.nb.spectest.core.STNodeAssembly},
* which is merely backed by a {@link List} of {@link STNode}s. This view, however,
* imposes a structure over the nodes which is name, data, name, data, ... and so on.
* This is a convenient way to consume and validate Node data which follows the form:
* <pre>{@code
* *name1:*
* ```yaml
* document-property: version1
* ```
* *name2:*
* ```yaml
* document-property: version2
* ```
* *name3:*
* ```yaml
* document-property: version3
* ```
* }</pre>
*
* In this example, there are six consecutive nodes which contain 3 names and three fenced code sections.
*/
public class STNamedCodeTuples {
private final List<STNameAndCodeTuple> tuples;
public STNamedCodeTuples(List<STNameAndCodeTuple> tuples) {
this.tuples = tuples;
}
/**
* <p>For the STNamedCodeTuples view of a testable set of nodes, it is useful
* to see what the name structure looks like in order to conditionally
* match different types of testable sequences based on the asserted names
* in the documentation. This will return the named, concatenated with "->",
* and sanitized with all non-alphanumeric and &gt; and $lt;, space, underscore
* and dash removed.</p>
*
* <p>For the example structure at the class level, this would look like:
* <pre>{@code name1->name2->name3}</pre></p>
* @return A signature of the node sequences based on the name elements
*/
public String getTypeSignature() {
StringBuilder sb = new StringBuilder();
for (STNameAndCodeTuple tuple : tuples) {
sb.append(tuple.getDesc()).append("->");
}
sb.setLength(sb.length()-"->".length());
return sb.toString().replaceAll("[^-a-zA-Z0-9<> _]", "");
}
public STNameAndCodeTuple get(int i) {
return tuples.get(i);
}
}

View File

@ -0,0 +1,21 @@
/*
* Copyright (c) 2022 nosqlbench
*
* Licensed 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 io.nosqlbench.nb.spectest.testtypes;
public class STNamedNodes {
}

View File

@ -0,0 +1,25 @@
/*
* Copyright (c) 2022 nosqlbench
*
* Licensed 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 io.nosqlbench.nb.spectest.testtypes;
import java.nio.file.Path;
public interface STNodeReference {
Path getPath();
int getLineNumber();
}

View File

@ -0,0 +1,23 @@
/*
* Copyright (c) 2022 nosqlbench
*
* Licensed 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 io.nosqlbench.nb.spectest.types;
import io.nosqlbench.nb.spectest.core.STNodeAssembly;
public interface STAssemblyValidator {
void validate(STNodeAssembly assembly);
}

View File

@ -0,0 +1,112 @@
/*
* Copyright (c) 2022 nosqlbench
*
* Licensed 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 io.nosqlbench.nb.spectest.types;
import com.vladsch.flexmark.util.ast.Node;
import io.nosqlbench.nb.spectest.core.SpecTest;
import io.nosqlbench.nb.spectest.core.SpecTestBuilder;
import io.nosqlbench.nb.spectest.loaders.STNodePredicate;
import io.nosqlbench.nb.spectest.loaders.STNodePredicates;
import java.nio.file.Path;
public interface STBuilderFacets {
interface All extends WantsPaths, WantsPathsOrScannersOrValidators, WantsScannersOrValidators {}
interface WantsPaths {
/**
* Provide additional path or paths, which can be either a readable file or a directory containing readable
* files or other directories. Only files which match a markdown path with a '.md' extension
* will be validated.
* @param paths Paths to test
* @return this {@link SpecTestBuilder} for method chaining
*/
WantsPathsOrScannersOrValidators paths(Path... paths);
/**
* Provide additional path or paths, which can be either a readable file or a directory containing readable
* files or other directories. Only files which match a markdown path with a '.md' extension
* will be validated.
* @param paths Paths to test
* @return this {@link SpecTestBuilder} for method chaining
*/
default WantsPathsOrScannersOrValidators paths(String... paths) {
Path[] args = new Path[paths.length];
for (int i = 0; i < paths.length; i++) {
args[i]=Path.of(paths[i]);
}
return this.paths(args);
}
/**
* Provide an additional path, which can be either a readable file or a directory containing readable
* files or other directories. Only files which match a markdown path with a '.md' extension
* will be validated.
* @param path Paths to test
* @return this {@link SpecTestBuilder} for method chaining
*/
default WantsPathsOrScannersOrValidators path(Path path) {
return this.paths(path);
}
/**
* Provide an additional path or paths, which can be either a readable file or a directory containing readable
* files or other directories. Only files which match a markdown path with a '.md' extension
* will be validated.
* @param path Paths to test
* @return this {@link SpecTestBuilder} for method chaining
*/
default WantsPathsOrScannersOrValidators path(String path) {
return this.paths(Path.of(path));
}
}
interface WantsPathsOrScannersOrValidators extends WantsPaths, WantsScannersOrValidators {
}
interface WantsScannersOrValidators extends WantsValidatorsOrDone {
/**
* Attach a set of scanners against {@link Node} sequences
* @param scanners
* @return
*/
WantsValidatorsOrDone scanners(STPathLoader... scanners);
WantsValidatorsOrDone scanners(STNodePredicate predicate);
/**
* <P>Attach a {@link Node} scanner to this spec test builder by describing the
* sequence of nodes which are valid, with no extra node types allowed between the
* specified elements. This is a direct sequence matcher that is checked at each
* position in the parsed element stream. When a sequence is found, the search
* is resumed on the next element not included in the result.</P>
*
* <P>The predicates can be one of the types supported by {@link STNodePredicate}
* and {@link STNodePredicates}.</P>
*
* @param predicates The pattern to match
* @return this SpecTestBuilder for builder method chaining
*/
WantsValidatorsOrDone matchNodes(Object... predicates);
}
interface WantsValidatorsOrDone extends Done{
Done validators(STAssemblyValidator... validators);
}
interface Done {
SpecTest build();
}
}

View File

@ -0,0 +1,27 @@
/*
* Copyright (c) 2022 nosqlbench
*
* Licensed 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 io.nosqlbench.nb.spectest.types;
import com.vladsch.flexmark.util.ast.Node;
import io.nosqlbench.nb.spectest.core.STNodeAssembly;
import java.nio.file.Path;
import java.util.List;
import java.util.function.BiFunction;
public interface STNodeLoader extends BiFunction<Path, Node, List<STNodeAssembly>> {
}

View File

@ -0,0 +1,32 @@
/*
* Copyright (c) 2022 nosqlbench
*
* Licensed 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 io.nosqlbench.nb.spectest.types;
import io.nosqlbench.nb.spectest.core.STNodeAssembly;
import io.nosqlbench.nb.spectest.loaders.STNodePredicates;
import java.nio.file.Path;
import java.util.List;
import java.util.function.Function;
/**
* A NodeScanner extracts a sequence of {@link STNodeAssembly}s, typically
* by use of {@link STNodePredicates}
*/
public interface STPathLoader extends Function<Path, List<STNodeAssembly>> {
}

View File

@ -0,0 +1,24 @@
/*
* Copyright (c) 2022 nosqlbench
*
* Licensed 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 io.nosqlbench.nb.spectest.types;
import io.nosqlbench.nb.spectest.core.STNodeAssembly;
import java.util.function.Consumer;
public interface STTypedAssembly extends Consumer<STNodeAssembly> {
}

View File

@ -0,0 +1,35 @@
/*
* Copyright (c) 2022 nosqlbench
*
* Licensed 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 io.nosqlbench.nb.spectest;
import io.nosqlbench.nb.spectest.loaders.STFileScanner;
import org.junit.jupiter.api.Test;
import java.nio.file.Path;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
public class STFileScannerTest {
@Test
public void findTestSpecs() {
List<Path> found = STFileScanner.findMatching(".*\\.md", Path.of("src/test/resources"));
assertThat(found).contains(Path.of("src/test/resources/spectestdir/scanner-test-file.md"));
}
}

View File

@ -14,18 +14,18 @@
* limitations under the License.
*/
package io.nosqlbench.engine.api.activityconfig.rawyaml;
package io.nosqlbench.nb.spectest;
import com.vladsch.flexmark.ext.yaml.front.matter.YamlFrontMatterExtension;
import com.vladsch.flexmark.parser.Parser;
import com.vladsch.flexmark.util.ast.Node;
import io.nosqlbench.nb.spectest.loaders.STHeadingScanner;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
public class HeadingScannerTest {
public class STHeadingScannerTest {
Parser parser = Parser.builder().extensions(List.of(YamlFrontMatterExtension.create())).build();
@Test
@ -34,19 +34,19 @@ public class HeadingScannerTest {
.parse("# heading1\n\n## heading2\n\ntext\n\n# heading3")
.getFirstChild();
HeadingScanner scanner = new HeadingScanner(".");
STHeadingScanner scanner = new STHeadingScanner(".");
assertThat(scanner.update(node).toString()).isEqualTo("heading1"); // Paragraph
Assertions.assertThat(scanner.update(node).toString()).isEqualTo("heading1"); // Paragraph
node = node.getNext();
assertThat(scanner.update(node).toString()).isEqualTo("heading1.heading2"); // Paragraph
Assertions.assertThat(scanner.update(node).toString()).isEqualTo("heading1.heading2"); // Paragraph
node=node.getNext();
assertThat(scanner.update(node).toString()).isEqualTo("heading1.heading2"); // Paragraph
Assertions.assertThat(scanner.update(node).toString()).isEqualTo("heading1.heading2"); // Paragraph
node=node.getNext();
assertThat(scanner.update(node).toString()).isEqualTo("heading3"); // Paragraph
Assertions.assertThat(scanner.update(node).toString()).isEqualTo("heading3"); // Paragraph
scanner.index();
assertThat(scanner.toString()).isEqualTo("heading3 (01)"); // Paragraph
Assertions.assertThat(scanner.toString()).isEqualTo("heading3 (01)"); // Paragraph
node=node.getNext();
assertThat(node).isNull();
Assertions.assertThat(node).isNull();
}
}

View File

@ -0,0 +1,35 @@
/*
* Copyright (c) 2022 nosqlbench
*
* Licensed 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 io.nosqlbench.nb.spectest;
import io.nosqlbench.nb.spectest.loaders.STNodePredicate;
import org.junit.jupiter.api.Test;
import java.util.function.Predicate;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
public class STNodePredicateTest {
@Test
public void testInvalidPredicateType() {
Predicate<Number> numPredicate = s -> s.longValue()==1_000_000L;
assertThatThrownBy(() -> new STNodePredicate(numPredicate))
.isInstanceOf(ClassCastException.class);
}
}

View File

@ -0,0 +1,59 @@
/*
* Copyright (c) 2022 nosqlbench
*
* Licensed 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 io.nosqlbench.nb.spectest;
import com.vladsch.flexmark.ext.yaml.front.matter.YamlFrontMatterExtension;
import com.vladsch.flexmark.parser.Parser;
import com.vladsch.flexmark.util.ast.Document;
import io.nosqlbench.nb.spectest.core.STNodeAssembly;
import io.nosqlbench.nb.spectest.loaders.STDefaultNodeLoader;
import io.nosqlbench.nb.spectest.loaders.STNodePredicates;
import io.nosqlbench.nb.spectest.types.STNodeLoader;
import org.junit.jupiter.api.Test;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
public class STNodePredicatesTest {
@Test
public void testBackReferences() {
STNodePredicates predicateShouldMatch1 = new STNodePredicates(".*__\\w__.*", 0, 0);
STNodePredicates predicateShouldMatch2 = new STNodePredicates("__\\w__", 0, 0);
STNodePredicates predicateShouldNotMatch3 = new STNodePredicates("^__\\w__", 0, 0);
STNodePredicates predicateShouldNotMatch4 = new STNodePredicates("^__\\w__$", 0, 0);
String testMarkdown = """
paragraph contents with __a__.
paragraph contents with __b__.
paragraph contents with __c__.
""";
Parser parser = Parser.builder().extensions(List.of(YamlFrontMatterExtension.create())).build();
Document document = parser.parse(testMarkdown);
// STDefaultLoader scanner = new STDefaultLoader(predicates);
STNodeLoader scanner = new STDefaultNodeLoader(predicateShouldMatch1);
List<STNodeAssembly> assemblies = scanner.apply(null, document);
assertThat(assemblies).hasSizeGreaterThan(0);
System.out.print(assemblies);
}
}

View File

@ -0,0 +1 @@
# Test

View File

@ -42,6 +42,7 @@
<module>nbr-examples</module>
<module>nb-api</module>
<module>nb-annotations</module>
<module>nb-spectest</module>
<module>engine-api</module>
<module>engine-core</module>
<module>engine-extensions</module>