diff --git a/tests/json/t-json.c b/tests/json/t-json.c index f574f57e..e1fb6c1e 100644 --- a/tests/json/t-json.c +++ b/tests/json/t-json.c @@ -1,499 +1,499 @@ /* t-json.c - Regression test. * Copyright (C) 2018 Bundesamt für Sicherheit in der Informationstechnik * Software engineering by Intevation GmbH * * This file is part of GPGME. * * GPGME is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * GPGME is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this program; if not, see . * SPDX-License-Identifier: LGPL-2.1-or-later */ #ifdef HAVE_CONFIG_H # include #endif #include #include #include #include #include #include #include #include "../gpg/t-support.h" #include "../../src/cJSON.h" /* Register tests here */ static const char*tests[] = { "t-config", "t-version", "t-keylist", "t-keylist-secret", "t-decrypt", "t-config-opt", "t-encrypt", "t-encrypt-sign", "t-sign", "t-verify", "t-decrypt-verify", "t-export", "t-createkey", "t-export-secret-info", "t-chunking", "t-sig-notations", /* For these two the order is important * as t-import imports the deleted key from t-delete */ "t-delete", "t-import", NULL }; static int verbose = 0; /* Read the next number in the version string STR and return it in *NUMBER. Return a pointer to the tail of STR after parsing, or *NULL if the version string was invalid. */ static const char * parse_version_number (const char *str, int *number) { #define MAXVAL ((INT_MAX - 10) / 10) int val = 0; /* Leading zeros are not allowed. */ if (*str == '0' && isdigit(str[1])) return NULL; while (isdigit (*str) && val <= MAXVAL) { val *= 10; val += *(str++) - '0'; } *number = val; return val > MAXVAL ? NULL : str; } /* Parse the version string STR in the format MAJOR.MINOR.MICRO (for example, 9.3.2) and return the components in MAJOR, MINOR and MICRO as integers. The function returns the tail of the string that follows the version number. This might be the empty string if there is nothing following the version number, or a patchlevel. The function returns NULL if the version string is not valid. */ static const char * parse_version_string (const char *str, int *major, int *minor, int *micro) { str = parse_version_number (str, major); if (!str || *str != '.') return NULL; str++; str = parse_version_number (str, minor); if (!str || *str != '.') return NULL; str++; str = parse_version_number (str, micro); if (!str) return NULL; /* A patchlevel might follow. */ return str; } /* Return true if MY_VERSION is at least REQ_VERSION, and false otherwise. */ static int compare_versions (const char *my_version, const char *rq_version) { int my_major, my_minor, my_micro; int rq_major, rq_minor, rq_micro; const char *my_plvl, *rq_plvl; if (!rq_version) return 1; if (!my_version) return 0; my_plvl = parse_version_string (my_version, &my_major, &my_minor, &my_micro); if (!my_plvl) return 0; rq_plvl = parse_version_string (rq_version, &rq_major, &rq_minor, &rq_micro); if (!rq_plvl) return 0; if (my_major > rq_major || (my_major == rq_major && my_minor > rq_minor) || (my_major == rq_major && my_minor == rq_minor && my_micro > rq_micro) || (my_major == rq_major && my_minor == rq_minor && my_micro == rq_micro && strcmp (my_plvl, rq_plvl) >= 0)) return 1; return 0; } /* Return true if we have the required gpg version. This should probably go into gpgrt or gpgme proper. */ static int check_gpg_version (const char *req_version) { gpgme_engine_info_t engine_info; init_gpgme (GPGME_PROTOCOL_OpenPGP); fail_if_err (gpgme_get_engine_info (&engine_info)); for (; engine_info; engine_info = engine_info->next) if (engine_info->protocol == GPGME_PROTOCOL_OpenPGP) break; test (engine_info); return compare_versions (engine_info->version, req_version); } static char * get_file (const char *fname) { gpg_error_t err; gpgrt_stream_t fp; struct stat st; char *buf; size_t buflen; fp = gpgrt_fopen (fname, "r"); if (!fp) { err = gpg_error_from_syserror (); fprintf (stderr, "Error: can't open '%s': %s\n", fname, gpg_strerror (err)); return NULL; } if (fstat (gpgrt_fileno(fp), &st)) { err = gpg_error_from_syserror (); fprintf (stderr, "Error: can't stat '%s': %s\n", fname, gpg_strerror (err)); gpgrt_fclose (fp); return NULL; } buflen = st.st_size; buf = malloc (buflen+1); if (!buf) { fprintf (stderr, "Error: no mem\n"); gpgrt_fclose (fp); return NULL; } if (gpgrt_fread (buf, buflen, 1, fp) != 1) { err = gpg_error_from_syserror (); fprintf (stderr, "error reading '%s': %s\n", fname, gpg_strerror (err)); gpgrt_fclose (fp); free (buf); return NULL; } buf[buflen] = 0; gpgrt_fclose (fp); return buf; } /* Check that the element needle exists in hay. Returns 0 if the needle was found. */ int test_contains (cjson_t needle, cjson_t hay) { if (verbose == 2) fprintf (stderr, "%s: -------checking-------- " "\n%s\n -------against-------- \n%s\n", nonnull (needle->string), cJSON_Print (needle), cJSON_Print (hay)); /* Type check. This automatically checks bool vals and NULL */ if (needle->type != hay->type) { if (verbose) fprintf (stderr, "%s: type mismatch expected %i got %i\n", nonnull (needle->string), needle->type, hay->type); return 1; } /* First the simple types */ if (cjson_is_number (needle)) { if (needle->valueint != hay->valueint) { if (verbose) fprintf (stderr, "%s: value mismatch. Expected %i got %i\n", nonnull (needle->string), needle->valueint, hay->valueint); return 1; } } if (cjson_is_string (needle)) { if (strcmp (needle->valuestring, hay->valuestring) && /* Use * as a general don't care placeholder */ strcmp (needle->valuestring, "*")) { if (verbose) fprintf (stderr, "%s: string mismatch Expected '%s' got '%s'\n", needle->string, needle->valuestring, hay->valuestring); return 1; } } /* Now the complex types */ if (needle->child) { if (!hay->child) { fprintf (stderr, "Depth mismatch. Expected child for %s\n", nonnull (needle->string)); } if (test_contains (needle->child, hay->child)) { int found = 0; for (cjson_t hit = hay->child; hit; hit = hit->next) { found |= !test_contains (needle->child, hit); if (found) { break; } } if (!found) { return 1; } } } if (needle->prev) { return 0; } /* Walk elements of an array */ for (cjson_t it = needle->next; it; it = it->next) { int found = 0; if (!it->string && it->child) { /* Try out all other anonymous children on the same level */ cjson_t hit = hay; /* Return to the beginning */ while (hit->prev) { hit = hit->prev; } for (; hit && hit->child; hit = hit->next) { found |= !test_contains (it->child, hit->child); if (found) { break; } } if (!found) { return 1; } continue; } /* Try the children in the haystack */ for (cjson_t hit = hay; hit; hit = hit->next) { if (hit->string && it->string && !strcmp (hit->string, it->string)) { found = 1; if (test_contains (it, hit)) { return 1; } } } if (!found) { if (verbose) fprintf (stderr, "Failed to find '%s' in list\n", nonnull (it->string)); return 1; } } return 0; } int check_response (const char *response, const char *expected) { cjson_t hay; cjson_t needle; int rc; size_t erroff; hay = cJSON_Parse (response, &erroff); if (!hay) { fprintf (stderr, "Failed to parse json at %i:\n%s\n", (int) erroff, response); return 1; } needle = cJSON_Parse (expected, &erroff); if (!needle) { fprintf (stderr, "Failed to parse json at %i:\n%s\n", (int) erroff, expected); cJSON_Delete (hay); return 1; } rc = test_contains (needle, hay); cJSON_Delete (needle); cJSON_Delete (hay); return rc; } int run_test (const char *test, const char *gpgme_json) { gpgme_ctx_t ctx; gpgme_data_t json_stdin = NULL; gpgme_data_t json_stdout = NULL; gpgme_data_t json_stderr = NULL; char *test_in; char *test_out; const char *argv[3]; char *response; char *expected = NULL; size_t response_size; int rc = 0; const char *top_srcdir = getenv ("top_srcdir"); if (!top_srcdir) { fprintf (stderr, "Error top_srcdir environment variable not set\n"); exit(1); } gpgrt_asprintf (&test_in, "%s/tests/json/%s.in.json", top_srcdir, test); gpgrt_asprintf (&test_out, "%s/tests/json/%s.out.json", top_srcdir, test); printf ("Running %s...\n", test); fail_if_err (gpgme_new (&ctx)); gpgme_set_protocol (ctx, GPGME_PROTOCOL_SPAWN); fail_if_err (gpgme_data_new_from_file (&json_stdin, test_in, 1)); fail_if_err (gpgme_data_new (&json_stdout)); fail_if_err (gpgme_data_new (&json_stderr)); argv[0] = gpgme_json; argv[1] = "-s"; argv[2] = NULL; fail_if_err (gpgme_op_spawn (ctx, gpgme_json, argv, json_stdin, json_stdout, json_stderr, 0)); response = gpgme_data_release_and_get_mem (json_stdout, &response_size); if (response_size) { expected = get_file (test_out); test (expected); rc = check_response (response, expected); } else { rc = 1; } if (!rc) { printf (" success\n"); gpgme_data_release (json_stderr); } else { char *buf; size_t size; buf = gpgme_data_release_and_get_mem (json_stderr, &size); printf (" failed%s\n", response_size ? "" : ", no response from gpgme-json"); if (size) { printf ("gpgme-json stderr:\n%.*s\n", (int)size, buf); } free (buf); } free (test_out); free (test_in); free (response); free (expected); gpgme_data_release (json_stdin); gpgme_release (ctx); return rc; } int main (int argc, char *argv[]) { const char *gpgme_json = getenv ("gpgme_json"); int last_argc = -1; if (argc) { argc--; argv++; } while (argc && last_argc != argc ) { last_argc = argc; if (!strcmp (*argv, "--verbose")) { verbose++; argc--; argv++; } } - if (!check_gpg_version ("2.2.0")) + if (!check_gpg_version ("2.1.18")) { /* Lets not break too much or have to test all combinations */ - printf ("Testsuite skipped. Minimum GnuPG version (2.2.0) " + printf ("Testsuite skipped. Minimum GnuPG version (2.1.18) " "not found.\n"); exit(0); } init_gpgme (GPGME_PROTOCOL_SPAWN); for (const char **test = tests; *test; test++) { if (run_test (*test, gpgme_json)) { exit(1); } } return 0; }