Add yajl parser tests

yajl_test.c is the test runner, from the yajl distribution.
The yajlTestConverter.pl script converts the yajl distribution's
test case files into a data structure in yajlTestCases.pm.
yajlTest.plt is a test director which reads the test cases from
yajlTestCases.pm and runs them through the yajl_test program.
This commit is contained in:
Andrew Johnson
2017-06-26 00:40:17 -05:00
parent 08ad4de161
commit c288de3bf2
5 changed files with 3493 additions and 0 deletions

View File

@@ -215,6 +215,10 @@ ipAddrToAsciiTest_SRCS += ipAddrToAsciiTest.cpp
testHarness_SRCS += ipAddrToAsciiTest.cpp
TESTS += ipAddrToAsciiTest
TESTPROD_HOST += yajl_test
yajl_test_SRCS += yajl_test.c
TESTS += yajlTest
# The testHarness runs all the test programs in a known working order.
testHarness_SRCS += epicsRunLibComTests.c

View File

@@ -0,0 +1,51 @@
#!/usr/bin/perl
#
# This is a test director for running yajl JSON parser tests.
# The tests are actually defined in the yajlTestCases.pm module,
# which is generated from the yajl cases by yajlTestConverter.pl
use strict;
use Test::More;
use IO::Handle;
use IPC::Open3;
# Load test cases
use lib "..";
use yajlTestCases;
my @cases = cases();
plan tests => scalar @cases;
# The yajl_test program reads JSON from stdin and sends a description
# of what it got to stdout, with errors going to stderr. We merge the
# two output streams for the purpose of checking the test results.
my $prog = './yajl_test';
$prog .= '.exe' if ($^O eq 'MSWin32') || ($^O eq 'cygwin');
foreach my $case (@cases) {
my $name = $case->{name};
my @opts = @{$case->{opts}};
my @input = @{$case->{input}};
my @gives = @{$case->{gives}};
my ($rx, $tx);
my $pid = open3($tx, $rx, 0, $prog, @opts);
# Send the test case, then EOF
print $tx join "\n", @input;
close $tx;
# Receive the result
my @result;
while (!$rx->eof) {
chomp(my $line = <$rx>);
push @result, $line;
}
close $rx;
# Clean up the child process
waitpid $pid, 0;
# Report the result of this test case
is_deeply(\@result, \@gives, $name);
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,98 @@
#!/usr/bin/perl
#
# This script converts the parsing test cases from the yajl release tree
# into the yajlTestCases module as used by libCom/test/yajlTest.plt
#
# Re-do this conversion and commit after checking out a new version of yajl
# from https://github.com/lloyd/yajl as follows:
# $ cd <base>/src/libCom/test
# $ perl yajlTestConverter.pl /path/to/yajl
# The tests are saved into the file yajlTestCases.pm in the src/libCom/test
# directory which will be read by the yajlTest.t test script.
use Data::Dumper;
my $yajl = shift @ARGV
or die "Usage: $0 /path/to/yajl\n";
my @files = glob "$yajl/test/parsing/cases/*.json";
my $caseFile = 'yajlTestCases.pm';
my @cases;
for my $file (@files) {
$file =~ m|/([afn][cgmp]_)?([^/]*)\.json$|;
my $allow = $1;
my $name = $2;
next if $name eq '';
my $case = { name => $name };
if ($allow eq 'ac_') {
$case->{opts} = ['-c'];
}
elsif ($allow eq 'ag_') {
$case->{opts} = ['-g'];
}
elsif ($allow eq 'am_') {
$case->{opts} = ['-m'];
}
elsif ($allow eq 'ap_') {
$case->{opts} = ['-p'];
}
else {
$case->{opts} = [];
}
my $input = slurp($file);
my @input = split "\n", $input;
push @input, '' if $input =~ m/\n$/;
$case->{input} = \@input;
my @gives = split "\n", slurp("$file.gold");
$case->{gives} = \@gives;
push @cases, $case;
}
# Configure Dumper() output
$Data::Dumper::Pad = ' ';
$Data::Dumper::Indent = 1;
$Data::Dumper::Useqq = 1;
$Data::Dumper::Quotekeys = 0;
$Data::Dumper::Sortkeys = sub { return ['name', 'opts', 'input', 'gives'] };
my $data = Dumper(\@cases);
open my $out, '>', $caseFile
or die "Can't open/create $caseFile: $@\n";
print $out <<"EOF";
# Parser test cases from https://github.com/lloyd/yajl
#
# This file is generated, DO NOT EDIT!
#
# See comments in yajlTestConverter.pl for instructions on
# how to regenerate this file from the original yajl sources.
sub cases {
my$data
return \@{\$VAR1};
}
1;
EOF
close $out
or die "Problem writing $caseFile: $@\n";
exit 0;
sub slurp {
my ($file) = @_;
open my $in, '<', $file
or die "Can't open file $file: $!\n";
my $contents = do { local $/; <$in> };
return $contents;
}

294
src/libCom/test/yajl_test.c Normal file
View File

@@ -0,0 +1,294 @@
/*
* Copyright (c) 2007-2014, Lloyd Hilaiel <me@lloyd.io>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <yajl_parse.h>
#include <yajl_gen.h>
#include <assert.h>
/* memory debugging routines */
typedef struct
{
unsigned int numFrees;
unsigned int numMallocs;
/* XXX: we really need a hash table here with per-allocation
* information */
} yajlTestMemoryContext;
/* cast void * into context */
#define TEST_CTX(vptr) ((yajlTestMemoryContext *) (vptr))
static void yajlTestFree(void * ctx, void * ptr)
{
assert(ptr != NULL);
TEST_CTX(ctx)->numFrees++;
free(ptr);
}
static void * yajlTestMalloc(void * ctx, size_t sz)
{
assert(sz != 0);
TEST_CTX(ctx)->numMallocs++;
return malloc(sz);
}
static void * yajlTestRealloc(void * ctx, void * ptr, size_t sz)
{
if (ptr == NULL) {
assert(sz != 0);
TEST_CTX(ctx)->numMallocs++;
} else if (sz == 0) {
TEST_CTX(ctx)->numFrees++;
}
return realloc(ptr, sz);
}
/* begin parsing callback routines */
#define BUF_SIZE 2048
static int test_yajl_null(void *ctx)
{
printf("null\n");
return 1;
}
static int test_yajl_boolean(void * ctx, int boolVal)
{
printf("bool: %s\n", boolVal ? "true" : "false");
return 1;
}
static int test_yajl_integer(void *ctx, long long integerVal)
{
printf("integer: %lld\n", integerVal);
return 1;
}
static int test_yajl_double(void *ctx, double doubleVal)
{
printf("double: %g\n", doubleVal);
return 1;
}
static int test_yajl_string(void *ctx, const unsigned char * stringVal,
size_t stringLen)
{
printf("string: '");
fwrite(stringVal, 1, stringLen, stdout);
printf("'\n");
return 1;
}
static int test_yajl_map_key(void *ctx, const unsigned char * stringVal,
size_t stringLen)
{
char * str = (char *) malloc(stringLen + 1);
str[stringLen] = 0;
memcpy(str, stringVal, stringLen);
printf("key: '%s'\n", str);
free(str);
return 1;
}
static int test_yajl_start_map(void *ctx)
{
printf("map open '{'\n");
return 1;
}
static int test_yajl_end_map(void *ctx)
{
printf("map close '}'\n");
return 1;
}
static int test_yajl_start_array(void *ctx)
{
printf("array open '['\n");
return 1;
}
static int test_yajl_end_array(void *ctx)
{
printf("array close ']'\n");
return 1;
}
static yajl_callbacks callbacks = {
test_yajl_null,
test_yajl_boolean,
test_yajl_integer,
test_yajl_double,
NULL,
test_yajl_string,
test_yajl_start_map,
test_yajl_map_key,
test_yajl_end_map,
test_yajl_start_array,
test_yajl_end_array
};
static void usage(const char * progname)
{
fprintf(stderr,
"usage: %s [options]\n"
"Parse input from stdin as JSON and ouput parsing details "
"to stdout\n"
" -b set the read buffer size\n"
" -c allow comments\n"
" -g allow *g*arbage after valid JSON text\n"
" -h print this help message\n"
" -m allows the parser to consume multiple JSON values\n"
" from a single string separated by whitespace\n"
" -p partial JSON documents should not cause errors\n",
progname);
exit(1);
}
int
main(int argc, char ** argv)
{
yajl_handle hand;
const char * fileName = NULL;
static unsigned char * fileData = NULL;
FILE *file;
size_t bufSize = BUF_SIZE;
yajl_status stat;
size_t rd;
int i, j;
/* memory allocation debugging: allocate a structure which collects
* statistics */
yajlTestMemoryContext memCtx = { 0,0 };
/* memory allocation debugging: allocate a structure which holds
* allocation routines */
yajl_alloc_funcs allocFuncs = {
yajlTestMalloc,
yajlTestRealloc,
yajlTestFree,
(void *) NULL
};
allocFuncs.ctx = (void *) &memCtx;
/* allocate the parser */
hand = yajl_alloc(&callbacks, &allocFuncs, NULL);
/* check arguments. We expect exactly one! */
for (i=1;i<argc;i++) {
if (!strcmp("-c", argv[i])) {
yajl_config(hand, yajl_allow_comments, 1);
} else if (!strcmp("-b", argv[i])) {
if (++i >= argc) usage(argv[0]);
/* validate integer */
for (j=0;j<(int)strlen(argv[i]);j++) {
if (argv[i][j] <= '9' && argv[i][j] >= '0') continue;
fprintf(stderr, "-b requires an integer argument. '%s' "
"is invalid\n", argv[i]);
usage(argv[0]);
}
bufSize = atoi(argv[i]);
if (!bufSize) {
fprintf(stderr, "%zu is an invalid buffer size\n",
bufSize);
}
} else if (!strcmp("-g", argv[i])) {
yajl_config(hand, yajl_allow_trailing_garbage, 1);
} else if (!strcmp("-h", argv[i])) {
usage(argv[0]);
} else if (!strcmp("-m", argv[i])) {
yajl_config(hand, yajl_allow_multiple_values, 1);
} else if (!strcmp("-p", argv[i])) {
yajl_config(hand, yajl_allow_partial_values, 1);
} else {
fileName = argv[i];
break;
}
}
fileData = (unsigned char *) malloc(bufSize);
if (fileData == NULL) {
fprintf(stderr,
"failed to allocate read buffer of %zu bytes, exiting.",
bufSize);
yajl_free(hand);
exit(2);
}
if (fileName)
{
file = fopen(fileName, "r");
}
else
{
file = stdin;
}
for (;;) {
rd = fread((void *) fileData, 1, bufSize, file);
if (rd == 0) {
if (!feof(stdin)) {
fprintf(stderr, "error reading from '%s'\n", fileName);
}
break;
}
/* read file data, now pass to parser */
stat = yajl_parse(hand, fileData, rd);
if (stat != yajl_status_ok) break;
}
stat = yajl_complete_parse(hand);
if (stat != yajl_status_ok)
{
unsigned char * str = yajl_get_error(hand, 0, fileData, rd);
fflush(stdout);
fprintf(stderr, "%s", (char *) str);
yajl_free_error(hand, str);
}
yajl_free(hand);
free(fileData);
if (fileName)
{
fclose(file);
}
/* finally, print out some memory statistics */
/* (lth) only print leaks here, as allocations and frees may vary depending
* on read buffer size, causing false failures.
*
* printf("allocations:\t%u\n", memCtx.numMallocs);
* printf("frees:\t\t%u\n", memCtx.numFrees);
*/
fflush(stderr);
fflush(stdout);
printf("memory leaks:\t%u\n", memCtx.numMallocs - memCtx.numFrees);
return 0;
}