From c0b68089adf8ea81e63885fe80b0f0ef44cdfbeb Mon Sep 17 00:00:00 2001
From: Mark Dilger <mark.dilger@enterprisedb.com>
Date: Sun, 26 Jan 2020 10:41:37 -0800
Subject: [PATCH 2/2] Adding frontend tests for json parser.

Creating tests in new directory src/test/bin for testing
that the json parser can be included and used from within
a frontend standalone binary.
---
 src/Makefile                    |  4 +-
 src/test/Makefile               |  7 ++--
 src/test/bin/.gitignore         |  1 +
 src/test/bin/Makefile           | 41 ++++++++++++++++++++
 src/test/bin/README             | 15 ++++++++
 src/test/bin/t/001_test_json.pl | 47 +++++++++++++++++++++++
 src/test/bin/test_json.c        | 67 +++++++++++++++++++++++++++++++++
 7 files changed, 178 insertions(+), 4 deletions(-)
 create mode 100644 src/test/bin/.gitignore
 create mode 100644 src/test/bin/Makefile
 create mode 100644 src/test/bin/README
 create mode 100644 src/test/bin/t/001_test_json.pl
 create mode 100644 src/test/bin/test_json.c

diff --git a/src/Makefile b/src/Makefile
index bcdbd9588a..ccd4bab0de 100644
--- a/src/Makefile
+++ b/src/Makefile
@@ -2,7 +2,8 @@
 #
 # Makefile for src
 #
-# Copyright (c) 1994, Regents of the University of California
+# Portions Copyright (c) 1994, Regents of the University of California
+# Portions Copyright (c) 1996-2020, PostgreSQL Global Development Group
 #
 # src/Makefile
 #
@@ -27,6 +28,7 @@ SUBDIRS = \
 	bin \
 	pl \
 	makefiles \
+	test/bin \
 	test/regress \
 	test/isolation \
 	test/perl
diff --git a/src/test/Makefile b/src/test/Makefile
index efb206aa75..e24732f190 100644
--- a/src/test/Makefile
+++ b/src/test/Makefile
@@ -12,7 +12,7 @@ subdir = src/test
 top_builddir = ../..
 include $(top_builddir)/src/Makefile.global
 
-SUBDIRS = perl regress isolation modules authentication recovery subscription
+SUBDIRS = bin perl regress isolation modules authentication recovery subscription
 
 # Test suites that are not safe by default but can be run if selected
 # by the user via the whitespace-separated list in variable
@@ -40,10 +40,11 @@ endif
 ALWAYS_SUBDIRS = $(filter-out $(SUBDIRS),examples kerberos ldap locale thread ssl)
 
 # We want to recurse to all subdirs for all standard targets, except that
-# installcheck and install should not recurse into the subdirectory "modules".
+# installcheck and install should not recurse into the subdirectory "modules"
+# nor "bin".
 
 recurse_alldirs_targets := $(filter-out installcheck install, $(standard_targets))
-installable_dirs := $(filter-out modules, $(SUBDIRS))
+installable_dirs := $(filter-out modules bin, $(SUBDIRS))
 
 $(call recurse,$(recurse_alldirs_targets))
 $(call recurse,installcheck, $(installable_dirs))
diff --git a/src/test/bin/.gitignore b/src/test/bin/.gitignore
new file mode 100644
index 0000000000..6709c749d8
--- /dev/null
+++ b/src/test/bin/.gitignore
@@ -0,0 +1 @@
+test_json
diff --git a/src/test/bin/Makefile b/src/test/bin/Makefile
new file mode 100644
index 0000000000..3eee9091bc
--- /dev/null
+++ b/src/test/bin/Makefile
@@ -0,0 +1,41 @@
+#-------------------------------------------------------------------------
+#
+# Makefile for src/test/bin
+#
+# Portions Copyright (c) 1996-2020, PostgreSQL Global Development Group
+# Portions Copyright (c) 1994, Regents of the University of California
+#
+# src/bin/bin/Makefile
+#
+#-------------------------------------------------------------------------
+
+PGFILEDESC = "bin - the PostgreSQL standalone code binaries for testing"
+PGAPPICON=win32
+
+subdir = src/test/bin
+top_builddir = ../../..
+include $(top_builddir)/src/Makefile.global
+
+# make this available to TAP test scripts
+export with_readline
+
+REFDOCDIR= $(top_srcdir)/doc/src/sgml/ref
+
+override CPPFLAGS := -I. -I$(srcdir) -I$(libpq_srcdir) $(CPPFLAGS)
+LDFLAGS_INTERNAL += -L$(top_builddir)/src/fe_utils -lpgfeutils $(libpq_pgport)
+
+OBJS = \
+	$(WIN32RES) \
+	test_json.o
+
+all: test_json
+
+test_json: $(OBJS) | submake-libpq submake-libpgport submake-libpgfeutils
+	$(CC) $(CFLAGS) $(OBJS) $(LDFLAGS) $(LDFLAGS_EX) $(LIBS) -o $@$(X)
+
+check:
+	PATH="$(abs_top_builddir)/src/test/bin:$$PATH" $(prove_check)
+
+clean distclean maintainer-clean:
+	rm -f test_json$(X) $(OBJS)
+	rm -rf tmp_check
diff --git a/src/test/bin/README b/src/test/bin/README
new file mode 100644
index 0000000000..214671af88
--- /dev/null
+++ b/src/test/bin/README
@@ -0,0 +1,15 @@
+src/test/bin/README
+
+Binary executable tests
+=======================
+
+This directory contains a set of programs that exercise functionality declared
+in src/include/common and defined in src/common.  The purpose of these programs
+is to verify that code intended to work both from frontend and backend code do
+indeed work when compiled and used in frontend code.  The structure of this
+directory makes no attempt to test that such code works in the backend, as the
+backend has its own tests already, and presumably those tests sufficiently
+exercide the code as used by it.
+
+These test programs are part of the  tap-test suite.  Configure with tap tests
+enabled or these tests will be skipped.
diff --git a/src/test/bin/t/001_test_json.pl b/src/test/bin/t/001_test_json.pl
new file mode 100644
index 0000000000..eb85e1a07b
--- /dev/null
+++ b/src/test/bin/t/001_test_json.pl
@@ -0,0 +1,47 @@
+# Basic logical replication test
+use strict;
+use warnings;
+use TestLib ();
+use Cwd;
+
+use Test::More tests => 83;
+
+# There doesn't seem to be any easy way to get TestLib to use the binaries from
+# our directory, so we hack up a path to our binary and run that directly.  This
+# seems brittle enough that some other solution should be found, if possible.
+
+my $test_json = join('/', $ENV{TESTDIR}, 'test_json');
+
+ok(-f $test_json, "test_json file exists");
+ok(-x $test_json, "test_json file is executable");
+
+# Verify some valid JSON is accepted by our parser
+TestLib::command_like( [$test_json, q/null/       ], qr{\bVALID\b}, "null");
+TestLib::command_like( [$test_json, q/{}/         ], qr{\bVALID\b}, "empty object");
+TestLib::command_like( [$test_json, q/[]/         ], qr{\bVALID\b}, "empty array");
+TestLib::command_like( [$test_json, q/-12345/     ], qr{\bVALID\b}, "negative integer");
+TestLib::command_like( [$test_json, q/-1/         ], qr{\bVALID\b}, "negative integer");
+TestLib::command_like( [$test_json, q/0/          ], qr{\bVALID\b}, "zero");
+TestLib::command_like( [$test_json, q/1/          ], qr{\bVALID\b}, "positive integer");
+TestLib::command_like( [$test_json, q/12345/      ], qr{\bVALID\b}, "positive integer");
+TestLib::command_like( [$test_json, q/-1.23456789/], qr{\bVALID\b}, "negative float");
+TestLib::command_like( [$test_json, q/1.23456789/ ], qr{\bVALID\b}, "positive float");
+TestLib::command_like( [$test_json, q/{"a": "b"}/ ], qr{\bVALID\b}, "object");
+TestLib::command_like( [$test_json, q/["a", "b"]/ ], qr{\bVALID\b}, "array");
+TestLib::command_like( [$test_json, q/"pigs feet"/], qr{\bVALID\b}, 'text string');
+
+# Verify some invalid JSON is rejected by our parser
+TestLib::command_like( [$test_json, q/{/          ], qr{\bINVALID\b}, 'unclosed object');
+TestLib::command_like( [$test_json, q/[/          ], qr{\bINVALID\b}, 'unclosed array');
+TestLib::command_like( [$test_json, q/(/          ], qr{\bINVALID\b}, 'unclosed parenthesis');
+TestLib::command_like( [$test_json, q/}/          ], qr{\bINVALID\b}, 'unopened object');
+TestLib::command_like( [$test_json, q/]/          ], qr{\bINVALID\b}, 'unopened array');
+TestLib::command_like( [$test_json, q/)/          ], qr{\bINVALID\b}, 'unopened parenthesis');
+TestLib::command_like( [$test_json, q/{{{}}/      ], qr{\bINVALID\b}, 'unbalanced object curlies');
+TestLib::command_like( [$test_json, q/{{}}}/      ], qr{\bINVALID\b}, 'unbalanced object curlies');
+TestLib::command_like( [$test_json, q/[[[]]/      ], qr{\bINVALID\b}, 'unbalanced array braces');
+TestLib::command_like( [$test_json, q/[[]]]/      ], qr{\bINVALID\b}, 'unbalanced array braces');
+TestLib::command_like( [$test_json, q/((())/      ], qr{\bINVALID\b}, 'unbalanced array braces');
+TestLib::command_like( [$test_json, q/(()))/      ], qr{\bINVALID\b}, 'unbalanced array braces');
+TestLib::command_like( [$test_json, q/1 7 13/     ], qr{\bINVALID\b}, 'integer sequence');
+TestLib::command_like( [$test_json, q/{"a", "b"}/ ], qr{\bINVALID\b}, 'mixed object and array syntax');
diff --git a/src/test/bin/test_json.c b/src/test/bin/test_json.c
new file mode 100644
index 0000000000..567a35f3b0
--- /dev/null
+++ b/src/test/bin/test_json.c
@@ -0,0 +1,67 @@
+/*
+ *	pg_test_json.c
+ *		tests validity of json strings against parser implementation.
+ */
+
+#include "postgres_fe.h"
+
+#include "common/jsonapi.h"
+#include "libpq-fe.h"
+
+static const char *progname;
+
+static void parse_json(const char *str);
+
+int
+main(int argc, char *argv[])
+{
+	int			argidx;
+
+	set_pglocale_pgservice(argv[0], PG_TEXTDOMAIN("pg_test_json"));
+	progname = get_progname(argv[0]);
+
+	/*
+	 * Make stdout unbuffered to match stderr; and ensure stderr is unbuffered
+	 * too, which it should already be everywhere except sometimes in Windows.
+	 */
+	setbuf(stdout, NULL);
+	setbuf(stderr, NULL);
+
+	if (argc > 1)
+	{
+		if (strcmp(argv[1], "--help") == 0 || strcmp(argv[1], "-?") == 0)
+		{
+			printf(_("Usage: %s jsonstr [, ...]\n"), progname);
+			exit(0);
+		}
+		if (strcmp(argv[1], "--version") == 0 || strcmp(argv[1], "-V") == 0)
+		{
+			puts("pg_test_json (PostgreSQL) " PG_VERSION);
+			exit(0);
+		}
+	}
+
+	for (argidx = 1; argidx < argc; argidx++)
+		parse_json(argv[argidx]);
+
+	return 0;
+}
+
+static void
+parse_json(const char *str)
+{
+	char *json;
+	unsigned int json_len;
+	JsonLexContext *lex;
+	int client_encoding;
+	JsonParseErrorType parse_result;
+
+	json_len = (unsigned int) strlen(str);
+	client_encoding = PQenv2encoding();
+
+	json = strdup(str);
+	lex = makeJsonLexContextCstringLen(json, strlen(json), client_encoding, true /* need_escapes */);
+	parse_result = pg_parse_json(lex, &nullSemAction);
+	fprintf(stdout, _("%s\n"), (JSON_SUCCESS == parse_result ? "VALID" : "INVALID"));
+	return;
+}
-- 
2.21.1 (Apple Git-122.3)

