Skip to content

Commit 5c7509a

Browse files
committed
#48 StoryMatcher
1 parent 835aea5 commit 5c7509a

File tree

4 files changed

+195
-17
lines changed

4 files changed

+195
-17
lines changed

pom.xml

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,11 +102,17 @@ SOFTWARE.
102102
<artifactId>cactoos</artifactId>
103103
<version>0.56.1</version>
104104
</dependency>
105+
<dependency>
106+
<groupId>org.junit.jupiter</groupId>
107+
<artifactId>junit-jupiter-api</artifactId>
108+
<version>5.10.5</version>
109+
<scope>provided</scope>
110+
</dependency>
105111
<dependency>
106112
<groupId>org.hamcrest</groupId>
107113
<artifactId>hamcrest</artifactId>
108114
<version>3.0</version>
109-
<scope>test</scope>
115+
<scope>provided</scope>
110116
</dependency>
111117
<dependency>
112118
<groupId>net.sf.saxon</groupId>
Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
/*
2+
* The MIT License (MIT)
3+
*
4+
* Copyright (c) 2022-2024 Yegor Bugayenko
5+
*
6+
* Permission is hereby granted, free of charge, to any person obtaining a copy
7+
* of this software and associated documentation files (the "Software"), to deal
8+
* in the Software without restriction, including without limitation the rights
9+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10+
* copies of the Software, and to permit persons to whom the Software is
11+
* furnished to do so, subject to the following conditions:
12+
*
13+
* The above copyright notice and this permission notice shall be included
14+
* in all copies or substantial portions of the Software.
15+
*
16+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18+
* FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
19+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22+
* SOFTWARE.
23+
*/
24+
package org.eolang.xax;
25+
26+
import com.jcabi.xml.XML;
27+
import com.jcabi.xml.XMLDocument;
28+
import com.jcabi.xml.XSLDocument;
29+
import com.yegor256.xsline.Shift;
30+
import com.yegor256.xsline.StClasspath;
31+
import com.yegor256.xsline.StXSL;
32+
import com.yegor256.xsline.TrDefault;
33+
import com.yegor256.xsline.Train;
34+
import com.yegor256.xsline.Xsline;
35+
import java.io.FileNotFoundException;
36+
import java.nio.file.Paths;
37+
import java.util.AbstractMap;
38+
import java.util.Arrays;
39+
import java.util.Collection;
40+
import java.util.LinkedList;
41+
import java.util.Map;
42+
import org.hamcrest.BaseMatcher;
43+
import org.hamcrest.Description;
44+
import org.junit.jupiter.api.Assumptions;
45+
import org.yaml.snakeyaml.Yaml;
46+
47+
/**
48+
* Hamcrest matcher for a YAML story.
49+
*
50+
* @since 0.1.0
51+
*/
52+
public final class StoryMatcher extends BaseMatcher<String> {
53+
54+
/**
55+
* The train to start with.
56+
*/
57+
private final Train<Shift> train = new TrDefault<>();
58+
59+
/**
60+
* The header of the match.
61+
*/
62+
private String header;
63+
64+
/**
65+
* The summary of the match.
66+
*/
67+
private String summary;
68+
69+
@Override
70+
@SuppressWarnings("unchecked")
71+
public boolean matches(final Object story) {
72+
final Map<String, Object> yaml = new Yaml().load(
73+
String.class.cast(story)
74+
);
75+
Assumptions.assumeTrue(yaml.get("skip") == null);
76+
final XML before = new XMLDocument(
77+
yaml.get("document").toString()
78+
);
79+
final XML after = this.xsline(yaml).pass(before);
80+
Object asserts = yaml.get("asserts");
81+
if (asserts == null) {
82+
asserts = Arrays.asList();
83+
}
84+
final Collection<Map.Entry<String, Boolean>> xpaths =
85+
new LinkedList<>();
86+
int failures = 0;
87+
for (final String xpath : (Iterable<String>) asserts) {
88+
final boolean success = !after.nodes(xpath).isEmpty();
89+
xpaths.add(
90+
new AbstractMap.SimpleImmutableEntry<>(
91+
xpath,
92+
success
93+
)
94+
);
95+
if (!success) {
96+
++failures;
97+
}
98+
}
99+
this.header = String.format(
100+
"All %d XPath expressions matched",
101+
xpaths.size()
102+
);
103+
final StringBuilder sum = new StringBuilder(1024)
104+
.append(String.format("%d XPath expression(s) failed:\n", failures));
105+
for (final Map.Entry<String, Boolean> ent : xpaths) {
106+
sum.append(" ");
107+
if (ent.getValue()) {
108+
sum.append("OK");
109+
} else {
110+
sum.append("FAIL");
111+
}
112+
sum.append(": ").append(ent.getKey()).append('\n');
113+
}
114+
sum
115+
.append("\nXML before XSL transformation:\n ")
116+
.append(before.toString().replace("\n", "\n "))
117+
.append(
118+
String.format(
119+
"\nXML after XSL transformation (%d->%d chars):\n ",
120+
before.toString().length(),
121+
after.toString().length()
122+
)
123+
)
124+
.append(after.toString().replace("\n", "\n "));
125+
this.summary = sum.toString();
126+
boolean match = true;
127+
for (final Map.Entry<String, Boolean> ent : xpaths) {
128+
if (!ent.getValue()) {
129+
match = false;
130+
break;
131+
}
132+
}
133+
return match;
134+
}
135+
136+
@Override
137+
public void describeTo(final Description desc) {
138+
desc.appendText(this.header);
139+
}
140+
141+
@Override
142+
public void describeMismatch(final Object log, final Description desc) {
143+
desc.appendText("\n").appendText(this.summary);
144+
}
145+
146+
/**
147+
* Build a transformation line.
148+
* @param yaml The YAML
149+
* @return The line
150+
*/
151+
@SuppressWarnings("unchecked")
152+
private Xsline xsline(final Map<String, Object> yaml) {
153+
Object sheets = yaml.get("sheets");
154+
if (sheets == null) {
155+
sheets = Arrays.asList();
156+
}
157+
Train<Shift> trn = this.train;
158+
for (final String sheet : (Iterable<String>) sheets) {
159+
if (sheet.startsWith("file://")) {
160+
try {
161+
trn = trn.with(
162+
new StXSL(
163+
new XSLDocument(Paths.get(sheet.substring(7)))
164+
)
165+
);
166+
} catch (final FileNotFoundException ex) {
167+
throw new IllegalArgumentException(ex);
168+
}
169+
} else {
170+
trn = trn.with(new StClasspath(sheet));
171+
}
172+
}
173+
return new Xsline(trn);
174+
}
175+
176+
}

src/main/java/org/eolang/xax/XaxStory.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,9 @@
4242
* The test scenario in YAML.
4343
*
4444
* @since 0.1.0
45+
* @deprecated This class is deprecated, use {@link StoryMatcher} instead.
4546
*/
47+
@Deprecated
4648
public final class XaxStory {
4749

4850
/**

src/test/java/org/eolang/xax/XaxStoryTest.java renamed to src/test/java/org/eolang/xax/StoryMatcherTest.java

Lines changed: 10 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -33,30 +33,24 @@
3333
import org.junit.jupiter.params.ParameterizedTest;
3434

3535
/**
36-
* Test case for {@link XaxStory}.
36+
* Test case for {@link StoryMatcher}.
3737
*
3838
* @since 0.1.0
3939
*/
40-
final class XaxStoryTest {
40+
final class StoryMatcherTest {
4141

4242
@Test
4343
void printsItself() {
4444
MatcherAssert.assertThat(
45-
"passes with no exceptions",
46-
new XaxStory(
45+
"finds errors in the story",
46+
new StoryMatcher().matches(
4747
new UncheckedText(
4848
new TextOf(
4949
new ResourceOf("org/eolang/xax/broken/bad-simple.yaml")
5050
)
5151
).asString()
5252
),
53-
Matchers.hasToString(
54-
Matchers.allOf(
55-
Matchers.containsString("Asserts:"),
56-
Matchers.containsString("false: /doc/xyz"),
57-
Matchers.containsString("false: /doc/foo")
58-
)
59-
)
53+
Matchers.is(false)
6054
);
6155
}
6256

@@ -65,18 +59,18 @@ void printsItself() {
6559
void validatesSimpleScenario(final String yaml) {
6660
MatcherAssert.assertThat(
6761
"passes with no exceptions",
68-
new XaxStory(yaml),
69-
Matchers.is(true)
62+
yaml,
63+
new StoryMatcher()
7064
);
7165
}
7266

7367
@ParameterizedTest
7468
@ClasspathSource(value = "org/eolang/xax/broken", glob = "**.yaml")
7569
void validatesBrokenScenario(final String yaml) {
7670
MatcherAssert.assertThat(
77-
"passes with no exceptions",
78-
new XaxStory(yaml),
79-
Matchers.not(Matchers.is(true))
71+
"reports ",
72+
yaml,
73+
Matchers.not(new StoryMatcher())
8074
);
8175
}
8276

0 commit comments

Comments
 (0)