Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve Literal Spec Test #103

Merged
merged 6 commits into from Aug 19, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/check.yml
Expand Up @@ -38,11 +38,11 @@ jobs:
submodules: true

- name: Install Dependencies (Linux)
run: sudo apt-get install -y valgrind libunistring-dev
run: sudo apt-get install -y valgrind libunistring-dev python3
if: "contains( matrix.os, 'ubuntu')"

- name: Install Dependencies (MacOS)
run: brew install libunistring
run: brew install libunistring python3
if: "contains( matrix.os, 'macos')"

- name: Create Build Environment
Expand Down
2 changes: 1 addition & 1 deletion README.md
Expand Up @@ -97,7 +97,7 @@ plus = "+";
$ cat data.json
[{"numbers": [1,2.0,3e1]},[true,false,null],"xyz"]

$ peppa ast -g json.peg -e entry data.json | python3 ./scripts/gendot.py | dot -Tsvg -o/tmp/data.svg
$ peppa ast -G json.peg -e entry data.json | python3 ./scripts/gendot.py | dot -Tsvg -o/tmp/data.svg
```

![Example JSON AST](docs/_static/readme-json-ast2.svg)
Expand Down
3 changes: 2 additions & 1 deletion peppa.c
Expand Up @@ -1630,7 +1630,8 @@ P4_CreateNode (const P4_String str,
P4_PRIVATE(void)
P4_DeleteNodeNode(P4_Grammar* grammar, P4_Node* node) {
if (node) {
P4_DeleteNodeUserData(grammar, node);
if (grammar->free_func && node->userdata)
grammar->free_func(node->userdata);
P4_FREE(node);
}
}
Expand Down
55 changes: 45 additions & 10 deletions scripts/check_spec.py
@@ -1,26 +1,61 @@
import subprocess
import sys
import json
import shlex

def test_spec():
specs_json = sys.argv[1]
executable = sys.argv[1]
specs_json = sys.argv[2]
with open(specs_json) as f:
specs = json.load(f)

failed, total = 0, 0
for spec in specs:
with open('grammar', 'wb') as grammar_file:
grammar_file.write(spec['grammar'].encode('utf-8'))
for test in spec['tests']:
command = f'./peppa -g grammar -e {spec["entry"]} -'
proc = subprocess.run(command, capture_output=True, shell=True, input=test['I'].encode('utf-8'))
total += 1
proc = subprocess.run(
shlex.split(executable) + [
'ast',
'--grammar', spec['grammar'],
'--grammar-entry', spec['entry'],
],
capture_output=True,
input=test['I'].encode('utf-8'),
)
if 'O' in test:
assert proc.returncode == 0, proc.stderr
output = json.loads(proc.stdout.decode('utf-8'))
expect = test['O']
assert output == expect, f'spec: {spec}'
if proc.returncode == 0:
output = json.loads(proc.stdout.decode('utf-8'))
expect = test['O']
if output != expect:
print(
f"GRAMMAR:\n{spec['grammar']}\n"
f"INPUT:\n{test['I']}\n"
f"OUTPUT:\n{test['O']}\n"
f"GOT:\n{output}\n"
)
failed += 1
else:
print(
f"GRAMMAR:\n{spec['grammar']}\n"
f"INPUT:\n{test['I']}\n"
f"OUTPUT:\n{test['O']}\n"
f"GOT:\n{proc.stderr.decode('utf-8')}\n"
)
failed += 1
else:
assert proc.returncode != 0, proc.stderr
assert proc.stderr.decode('utf-8').strip() == test['E'], proc.stderr
if proc.stderr.decode('utf-8').strip() != test['E']:
print(
f"GRAMMAR:\n{spec['grammar']}\n"
f"INPUT:\n{test['I']}\n"
f"ERROR:\n{test['E']}\n"
f"GOT:\n{proc.stderr.decode('utf-8')}"
)
failed += 1

print("total: %d, failed: %d" % (total, failed))
if failed:
exit(1)

if __name__ == '__main__':
test_spec()
51 changes: 31 additions & 20 deletions shell.c
Expand Up @@ -48,6 +48,7 @@ struct p4_args_t {
char* subcommand;
bool help;
bool version;
char* grammar_content;
FILE* grammar_file;
char* grammar_entry;
size_t arguments_count;
Expand All @@ -63,8 +64,9 @@ static int subcommand_usage(const char* name) {
printf("usage: %s [SUBCOMMAND] [OPTION]...\n\n", name);
printf(
"SUBCOMMAND: ast [OPTIONS]... [FILE]...\n\n"
" --grammar-file/-g FILE\trequired, path to peg grammar file\n"
" --grammar-entry/-e NAME\trequired, entry rule name in peg grammar\n"
" --grammar/-g FILE\tpeg grammar string\n"
" --grammar-file/-G FILE\tpath to peg grammar file\n"
" --grammar-entry/-e NAME\tentry rule name in peg grammar\n"
"\n"
"OPTION:\n\n"
" --help/-h\t\t\tprint help information\n"
Expand All @@ -73,7 +75,7 @@ static int subcommand_usage(const char* name) {
"EXAMPLE:\n\n"
);
printf(" $ %s --version\n", name);
printf(" $ %s ast -g json.peg -e entry data.json\n", name);
printf(" $ %s ast -G json.peg -e entry data.json\n", name);
return 0;
}

Expand Down Expand Up @@ -123,11 +125,12 @@ int init_args(p4_args_t* args, int argc, char* argv[]) {
{"version", no_argument, 0, 'V'},
{"help", no_argument, 0, 'h'},
{"grammar-entry", required_argument, 0, 'e'},
{"grammar-file", required_argument, 0, 'g'},
{"grammar-file", required_argument, 0, 'G'},
{"grammar", required_argument, 0, 'g'},
{0, 0, 0, 0}
};
int option_index = 0;
c = getopt_long (argc, argv, "Vhe:g:", long_options, &option_index);
c = getopt_long (argc, argv, "Vhe:g:G:", long_options, &option_index);
if (c == -1) break;
switch (c) {
case 0:
Expand All @@ -142,6 +145,9 @@ int init_args(p4_args_t* args, int argc, char* argv[]) {
args->grammar_entry = optarg;
break;
case 'g':
args->grammar_content = optarg;
break;
case 'G':
if (!(args->grammar_file = fopen(optarg, "r"))) {
perror(optarg);
return -1;
Expand All @@ -168,13 +174,8 @@ int subcommand_ast(p4_args_t* args) {
char *grammar_content = NULL, *input_content = NULL;
FILE* input_file = NULL;

if (args->arguments_count < 2) {
fprintf(stderr, "error: argument FILE is required\n");
abort(1);
}

if (args->grammar_file == NULL) {
fprintf(stderr, "error: --grammar-file/-g is required\n");
if (args->grammar_file == NULL && args->grammar_content == NULL) {
fprintf(stderr, "error: --grammar-file/-G or --grammar/-g is required\n");
abort(1);
}

Expand All @@ -183,20 +184,30 @@ int subcommand_ast(p4_args_t* args) {
abort(1);
}

if (!(input_file = fopen(args->arguments[1], "r"))) {
perror(args->arguments[1]);
abort(1);
if (args->grammar_content) {
grammar_content = strdup(args->grammar_content);
} else {
grammar_content = read_file(args->grammar_file);
}

if (args->arguments_count == 1) {
input_content = P4_MALLOC(sizeof(char) * 1024 * 1024);
scanf("%1048575[^\n]", input_content);
} else {
if (!(input_file = fopen(args->arguments[1], "r"))) {
perror(args->arguments[1]);
abort(1);
}
input_content = read_file(input_file);
}

grammar_content = read_file(args->grammar_file);
input_content = read_file(input_file);
err = print_ast(grammar_content, input_content, args->grammar_entry);

finalize:

P4_FREE(input_content);
P4_FREE(grammar_content);
fclose(input_file);
if (input_content) P4_FREE(input_content);
if (grammar_content) P4_FREE(grammar_content);
if (input_file) fclose(input_file);
return err;
}

Expand Down
14 changes: 14 additions & 0 deletions tests/CMakeLists.txt
Expand Up @@ -67,6 +67,20 @@ foreach(unity_testcase ${unity_testcases})

endforeach()

set(
spec_testcases
"test_literal_spec"
)

find_program(PYTHON python3)
foreach(spec_testcase ${spec_testcases})
add_test(
NAME "${spec_testcase}"
COMMAND "${PYTHON}" "${PROJECT_SOURCE_DIR}/scripts/check_spec.py" "${CMAKE_BINARY_DIR}/${P4_BIN}" "${PROJECT_SOURCE_DIR}/tests/${spec_testcase}.json"
WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}"
)
endforeach()

add_custom_target(
check
COMMAND ${CMAKE_COMMAND} -E create_symlink ${CMAKE_CURRENT_SOURCE_DIR}/json_test_parsing ${CMAKE_CURRENT_BINARY_DIR}/json_test_parsing
Expand Down
20 changes: 0 additions & 20 deletions tests/spec.json

This file was deleted.

66 changes: 66 additions & 0 deletions tests/test_literal_spec.json
@@ -0,0 +1,66 @@
[
{
"grammar": "R1 = \"1\";",
"entry": "R1",
"tests": [
{
"I": "1",
"O": [{"slice":[0,1],"type":"R1"}]
},
{
"I": "2",
"E": "MatchError: line 1:1, expect R1 (char '1')"
},
{
"I": "",
"E": "MatchError: line 1:1, expect R1 (char '1')"
}
]
},

{
"grammar": "R1 = \"a\";",
"entry": "R1",
"tests": [
{
"I": "a",
"O": [{"slice":[0,1],"type":"R1"}]
},
{
"I": "b",
"E": "MatchError: line 1:1, expect R1 (char 'a')"
},
{
"I": "",
"E": "MatchError: line 1:1, expect R1 (char 'a')"
}
]
},

{
"grammar": "R1 = \"Hello World\";",
"entry": "R1",
"tests": [
{
"I": "Hello World",
"O": [{"slice":[0,11],"type":"R1"}]
},
{
"I": "Hello Worl",
"E": "MatchError: line 1:1, expect R1 (len 11)"
},
{
"I": "hello world",
"E": "MatchError: line 1:1, expect R1 (char 'H')"
},
{
"I": "Hello World + More Text",
"O": [{"slice":[0,11],"type":"R1"}]
},
{
"I": "",
"E": "MatchError: line 1:1, expect R1 (char 'H')"
}
]
}
]