Skip to content

Commit 9f6c3c9

Browse files
jonasfjsigurdm
andauthored
Simple crash_tests for yaml_edit (#2285)
Co-authored-by: Sigurd Meldgaard <[email protected]>
1 parent 2340a3a commit 9f6c3c9

File tree

9 files changed

+391
-0
lines changed

9 files changed

+391
-0
lines changed
Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
@TestOn('vm')
6+
library;
7+
8+
import 'dart:io';
9+
import 'dart:isolate';
10+
11+
import 'package:test/test.dart';
12+
import 'package:yaml/yaml.dart';
13+
import 'package:yaml_edit/yaml_edit.dart';
14+
15+
final _scalarStyles = [
16+
ScalarStyle.ANY,
17+
ScalarStyle.PLAIN,
18+
ScalarStyle.LITERAL,
19+
ScalarStyle.FOLDED,
20+
ScalarStyle.SINGLE_QUOTED,
21+
ScalarStyle.DOUBLE_QUOTED,
22+
];
23+
24+
/// Files with tests that are broken, so we have to skip them
25+
final _skippedFiles = [
26+
'block_strings.yaml',
27+
'complex.yaml',
28+
'explicit_key_value.yaml',
29+
'mangled_json.yaml',
30+
'simple_comments.yaml',
31+
];
32+
33+
/// The crash tests will attempt to enumerate all JSON paths in each input
34+
/// document and then proceed to make arbitrary mutations trying to see if
35+
/// [YamlEditor] will crash. Arbitrary mutations include:
36+
/// - Remove each JSON path
37+
/// - Prepend and append to each list and map.
38+
/// - Set each string to 'hello world'
39+
/// - Set all numbers to 42
40+
///
41+
/// Input documents are loaded from: test/crash_test/testdata/*.yaml
42+
Future<void> main() async {
43+
final packageUri = await Isolate.resolvePackageUri(
44+
Uri.parse('package:yaml_edit/yaml_edit.dart'));
45+
46+
final testdataUri = packageUri!.resolve('../test/crash_test/testdata/');
47+
final testFiles = Directory.fromUri(testdataUri)
48+
.listSync()
49+
.whereType<File>()
50+
.where((f) => f.path.endsWith('.yaml'))
51+
.toList();
52+
53+
for (final f in testFiles) {
54+
final fileName = f.uri.pathSegments.last;
55+
final input = f.readAsStringSync();
56+
final root = YamlEditor(input);
57+
58+
test('$fileName is valid YAML', () {
59+
loadYamlNode(input);
60+
});
61+
62+
if (_skippedFiles.contains(fileName)) {
63+
test(
64+
'crash_test.dart for $fileName',
65+
() {},
66+
skip: 'Known failures in "$fileName"',
67+
);
68+
continue;
69+
}
70+
71+
for (final (path, node) in _allJsonPaths(root.parseAt([]))) {
72+
_testJsonPath(fileName, input, path, node);
73+
}
74+
}
75+
}
76+
77+
void _testJsonPath(
78+
String fileName,
79+
String input,
80+
Iterable<Object?> path,
81+
YamlNode node,
82+
) {
83+
final editorName = 'YamlEditor($fileName)';
84+
85+
// Try to remove the node
86+
test('$editorName.remove($path)', () {
87+
final editor = YamlEditor(input);
88+
editor.remove(path);
89+
});
90+
91+
// Try to update path to a string
92+
test('$editorName.update($path, \'updated string\')', () {
93+
final editor = YamlEditor(input);
94+
editor.update(path, 'updated string');
95+
});
96+
97+
// Try to update path to an integer
98+
test('$editorName.update($path, 42)', () {
99+
final editor = YamlEditor(input);
100+
editor.update(path, 42);
101+
});
102+
103+
// Try to set a multi-line string for each style
104+
for (final style in _scalarStyles) {
105+
test('$editorName.update($path, \'foo\\nbar\') as $style', () {
106+
final editor = YamlEditor(input);
107+
editor.update(path, YamlScalar.wrap('foo\nbar', style: style));
108+
});
109+
}
110+
111+
// If it's a list, we try to insert into the list for each index
112+
if (node is YamlList) {
113+
for (var i = 0; i < node.length + 1; i++) {
114+
test('$editorName.insertIntoList($path, $i, 42)', () {
115+
final editor = YamlEditor(input);
116+
editor.insertIntoList(path, i, 42);
117+
});
118+
119+
test('$editorName.insertIntoList($path, $i, \'new string\')', () {
120+
final editor = YamlEditor(input);
121+
editor.insertIntoList(path, i, 'new string');
122+
});
123+
124+
for (final style in _scalarStyles) {
125+
test('$editorName.insertIntoList($path, $i, \'foo\\nbar\') as $style',
126+
() {
127+
final editor = YamlEditor(input);
128+
editor.insertIntoList(
129+
path,
130+
i,
131+
YamlScalar.wrap(
132+
'foo\nbar',
133+
style: style,
134+
));
135+
});
136+
}
137+
}
138+
}
139+
140+
// If it's a map, we try to insert a new key (if the new-key name isn't used)
141+
if (node is YamlMap && !node.containsKey('new-key')) {
142+
final newPath = [...path, 'new-key'];
143+
144+
test('$editorName.update($newPath, 42)', () {
145+
final editor = YamlEditor(input);
146+
editor.update(newPath, 42);
147+
});
148+
149+
test('$editorName.update($newPath, \'new string\')', () {
150+
final editor = YamlEditor(input);
151+
editor.update(newPath, 'new string');
152+
});
153+
154+
for (final style in _scalarStyles) {
155+
test('$editorName.update($newPath, \'foo\\nbar\') as $style', () {
156+
final editor = YamlEditor(input);
157+
editor.update(
158+
newPath,
159+
YamlScalar.wrap(
160+
'foo\nbar',
161+
style: style,
162+
));
163+
});
164+
}
165+
}
166+
}
167+
168+
Iterable<(Iterable<Object?>, YamlNode)> _allJsonPaths(
169+
YamlNode node, [
170+
Iterable<Object?> parents = const [],
171+
]) sync* {
172+
yield (parents, node);
173+
174+
if (node is YamlMap) {
175+
for (final entry in node.nodes.entries) {
176+
final key = entry.key as YamlNode;
177+
final value = entry.value;
178+
yield* _allJsonPaths(value, [...parents, key.value]);
179+
}
180+
} else if (node is YamlList) {
181+
for (var i = 0; i < node.nodes.length; i++) {
182+
final value = node.nodes[i];
183+
yield* _allJsonPaths(value, [...parents, i]);
184+
}
185+
}
186+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
block-strings: # annoying comment (maybe)
2+
- folded:
3+
- clip: >
4+
first line
5+
6+
skipped line
7+
8+
9+
# This is a comment
10+
# this too!
11+
# And this one
12+
- strip: >+ # We can have comments here
13+
first line
14+
15+
skipped line
16+
17+
18+
- keep: >-
19+
first line
20+
21+
skipped line
22+
# comment why not!
23+
# and again
24+
- literal:
25+
- clip: |
26+
first line
27+
28+
skipped line
29+
30+
31+
- strip: |+
32+
first line
33+
34+
skipped line
35+
36+
37+
- keep: |-
38+
first line
39+
40+
skipped line
41+
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
# Start comment
2+
? string
3+
# why not here too!
4+
: hello world
5+
# top-level comment
6+
# comment again
7+
? block-strings # annoying comment (maybe)
8+
: - folded:
9+
- clip: >
10+
first line
11+
12+
skipped line
13+
14+
15+
# This is a comment
16+
# this too!
17+
# And this one
18+
- strip: >+ # We can have comments here
19+
first line
20+
21+
skipped line
22+
23+
24+
- keep: >-
25+
first line
26+
27+
skipped line
28+
# comment why not!
29+
# and again
30+
- literal:
31+
- clip: |
32+
first line
33+
34+
skipped line
35+
36+
37+
- strip: |+
38+
first line
39+
40+
skipped line
41+
42+
43+
- keep: |-
44+
first line
45+
46+
skipped line
47+
48+
49+
? key
50+
: value # Note: this works
51+
# This too ?
52+
? map
53+
: k1: 1
54+
k2: 2
55+
k3: 3
56+
? list
57+
: - 1
58+
- 2
59+
- 3
60+
? inlineMap
61+
: {k1: 1, k2: 2, k3: 3}
62+
? inlineList
63+
: [1, 2, 3]
64+
? complex-object
65+
: foo: 42
66+
bar:
67+
- 'test test'
68+
- |
69+
hello world
70+
- "test string"
71+
- {
72+
a: 1,
73+
b:
74+
'hello world'
75+
}
76+
? json-with-comments
77+
: {
78+
"key": "value",
79+
'string with newline': 'hello
80+
world'
81+
, 32 : 42
82+
, list :
83+
[ # We can make multi-line strings inline!
84+
'foo
85+
86+
87+
bar' # comment before comma
88+
, -32.4
89+
# Comment again.
90+
, # Comment on the comma!
91+
# trailing comma, why not
92+
]
93+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
? map
2+
: k1: 1
3+
k2: 2
4+
k3: 3
5+
? list
6+
: - 1
7+
- 2
8+
- 3
9+
? inlineMap
10+
: {k1: 1, k2: 2, k3: 3}
11+
? inlineList
12+
: [1, 2, 3]
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"string": "hello world",
3+
"map": {
4+
"foo": 42,
5+
"bar": "baz"
6+
},
7+
"list": [
8+
1,
9+
2,
10+
3
11+
]
12+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# Initial comment
2+
{
3+
"string": "hello world", # Great message
4+
"map": {
5+
"foo": 42, # Fantastic number
6+
"bar": "baz"
7+
},
8+
"list": [
9+
1, # This is a good list
10+
2, # This comment is good
11+
# But this?
12+
# Or this?
13+
3
14+
]
15+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"string":
3+
"hello world",
4+
"map": {
5+
"foo":
6+
42
7+
,"bar":
8+
"baz"
9+
}
10+
,"list": [
11+
1,2
12+
,
13+
3
14+
,]
15+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
string: 'hello world'
2+
map:
3+
foo: 42
4+
bar: 'baz'
5+
list:
6+
- 1
7+
- 2
8+
- 3
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# Initial comment
2+
string: 'hello world' # Comment
3+
map:
4+
foo: 42 # Best number
5+
bar: 'baz'
6+
list: # Great starter list
7+
- 1
8+
- 2
9+
- 3

0 commit comments

Comments
 (0)