text: /*************************************************************************\
  * Copyright (c) 2010 UChicago Argonne LLC, as Operator of Argonne
  *     National Laboratory.
  * Copyright (c) 2010 Brookhaven National Laboratory.
  * Copyright (c) 2010 Helmholtz-Zentrum Berlin
  *     fuer Materialien und Energie GmbH.
  * EPICS BASE is distributed subject to a Software License Agreement found
  * in file LICENSE that is included with this distribution.
   \*************************************************************************/
  
  /*
   *  Author: Andrew Johnson <anj@aps.anl.gov>
   *          Ralph Lange <Ralph.Lange@bessy.de>
   */
  
  #include "dbChannel.h"
  #include "dbStaticLib.h"
  #include "dbAccessDefs.h"
  #include "registry.h"
  #include "recSup.h"
  #include "epicsUnitTest.h"
  #include "testMain.h"
  #include "osiFileName.h"
  #include "errlog.h"
  
  /* Expected call bit definitions */
  #define e_start         0x00000001
  #define e_abort         0x00000002
  #define e_end           0x00000004
  #define e_null          0x00000008
  #define e_boolean       0x00000010
  #define e_integer       0x00000020
  #define e_double        0x00000040
  #define e_string        0x00000080
  #define e_start_map     0x00000100
  #define e_map_key       0x00000200
  #define e_end_map       0x00000400
  #define e_start_array   0x00000800
  #define e_end_array     0x00001000
  #define e_open          0x00002000
  #define e_reg_pre       0x00004000
  #define e_reg_post      0x00008000
  #define e_report        0x00010000
  #define e_close         0x00020000
  
  #define r_any (e_start | e_abort | e_end | \
          e_null | e_boolean | e_integer | e_double | e_string | \
          e_start_map | e_map_key | e_end_map | e_start_array | e_end_array)
  #define r_scalar (e_start | e_abort | e_end | \
          e_null | e_boolean | e_integer | e_double | e_string)
  
  unsigned int e, r;
  #define p_ret(x) return r & x ? parse_continue : parse_stop
  
  parse_result p_start(chFilter *filter)
  {
      testOk(e & e_start, "parse_start called");
      p_ret(e_start);
  }
  
  void p_abort(chFilter *filter)
  {
      testOk(e & e_abort, "parse_abort called");
  }
  
  parse_result p_end(chFilter *filter)
  {
      testOk(e & e_end, "parse_end called");
      p_ret(e_end);
  }
  
  parse_result p_null(chFilter *filter)
  {
      testOk(e & e_null, "parse_null called");
      p_ret(e_null);
  }
  parse_result p_boolean(chFilter *filter, int boolVal)
  {
      testOk(e & e_boolean, "parse_boolean called, val = %d", boolVal);
      p_ret(e_boolean);
  }
  parse_result p_integer(chFilter *filter, long integerVal)
  {
      testOk(e & e_integer, "parse_integer called, val = %ld", integerVal);
      p_ret(e_integer);
  }
  parse_result p_double(chFilter *filter, double doubleVal)
  {
      testOk(e & e_double, "parse_double called, val = %g", doubleVal);
      p_ret(e_double);
  }
  parse_result p_string(chFilter *filter, const char *stringVal, size_t stringLen)
  {
      testOk(e & e_string, "parse_string called, val = '%.*s'", (int) stringLen,
              stringVal);
      p_ret(e_string);
  }
  
  parse_result p_start_map(chFilter *filter)
  {
      testOk(e & e_start_map, "parse_start_map called");
      p_ret(e_start_map);
  }
  parse_result p_map_key(chFilter *filter, const char *key, size_t stringLen)
  {
      testOk(e & e_map_key, "parse_map_key called, key = '%.*s'", (int) stringLen, key);
      p_ret(e_map_key);
  }
  parse_result p_end_map(chFilter *filter)
  {
      testOk(e & e_end_map, "parse_end_map called");
      p_ret(e_end_map);
  }
  
  parse_result p_start_array(chFilter *filter)
  {
      testOk(e & e_start_array, "parse_start_array called");
      p_ret(e_start_array);
  }
  parse_result p_end_array(chFilter *filter)
  {
      testOk(e & e_end_array, "parse_end_array called");
      p_ret(e_end_array);
  }
  
  long c_open(chFilter *filter)
  {
      testOk(e & e_open, "channel_open called");
      return 0;
  }
  void c_reg_pre(chFilter *filter, chPostEventFunc **cb_out, void **arg_out, db_field_log *probe)
  {
      testOk(e & e_reg_pre, "channel_register_pre called");
  }
  void c_reg_post(chFilter *filter, chPostEventFunc **cb_out, void **arg_out, db_field_log *probe)
  {
      testOk(e & e_reg_post, "channel_register_post called");
  }
  void c_report(chFilter *filter, int level, const unsigned short indent)
  {
      testOk(e & e_report, "channel_report called, level = %d", level);
  }
  void c_close(chFilter *filter)
  {
      testOk(e & e_close, "channel_close called");
  }
  
  chFilterIf testIf =
      { NULL, p_start, p_abort, p_end, p_null, p_boolean, p_integer, p_double,
        p_string, p_start_map, p_map_key, p_end_map, p_start_array, p_end_array,
        c_open, c_reg_pre, c_reg_post, c_report, c_close };
  
  void dbTestIoc_registerRecordDeviceDriver(struct dbBase *);
  
  MAIN(testDbChannel)     /* dbChannelTest is an API routine... */
  {
      dbChannel *pch;
  
      testPlan(76);
  
      if (dbReadDatabase(&pdbbase, "dbTestIoc.dbd",
              "." OSI_PATH_LIST_SEPARATOR ".." OSI_PATH_LIST_SEPARATOR
              "../O.Common" OSI_PATH_LIST_SEPARATOR "O.Common", NULL))
          testAbort("Database description 'dbTestIoc.dbd' not found");
  
      dbTestIoc_registerRecordDeviceDriver(pdbbase);
      if (dbReadDatabase(&pdbbase, "xRecord.db",
              "." OSI_PATH_LIST_SEPARATOR "..", NULL))
          testAbort("Test database 'xRecord.db' not found");
  
      dbChannelInit();
  
      r = e = 0;
      /* dbChannelTest() checks record and field names */
      testOk1(!dbChannelTest("x.NAME"));
      testOk1(!dbChannelTest("x.INP"));
      testOk1(!dbChannelTest("x.VAL"));
      testOk1(!dbChannelTest("x."));
      testOk1(!dbChannelTest("x"));
      testOk(dbChannelTest("y"), "Test, nonexistent record");
      testOk(dbChannelTest("x.NOFIELD"), "Test, nonexistent field");
  
      /* dbChannelTest() allows but ignores field modifiers */
      testOk1(!dbChannelTest("x.NAME$"));
      testOk1(!dbChannelTest("x.{}"));
      testOk1(!dbChannelTest("x.VAL{\"json\":true}"));
  
      /* dbChannelCreate() accepts field modifiers */
      testOk1(!!(pch = dbChannelCreate("x.{}")));
      if (pch) dbChannelDelete(pch);
      testOk1(!!(pch = dbChannelCreate("x.VAL{}")));
      testOk1(pch && dbChannelElements(pch) == 1);
      if (pch) dbChannelDelete(pch);
      testOk1(!!(pch = dbChannelCreate("x.NAME$")));
      testOk1(pch && dbChannelFieldType(pch) == DBF_CHAR);
      testOk1(pch && dbChannelExportType(pch) == DBR_CHAR);
      testOk1(pch && dbChannelElements(pch) == PVNAME_STRINGSZ);
      if (pch) dbChannelDelete(pch);
      testOk1(!!(pch = dbChannelCreate("x.INP$")));
      testOk1(pch && dbChannelFieldType(pch) == DBF_INLINK);
      testOk1(pch && dbChannelExportType(pch) == DBR_CHAR);
      testOk1(pch && dbChannelElements(pch) > PVNAME_STRINGSZ);
      if (pch) dbChannelDelete(pch);
      testOk1(!!(pch = dbChannelCreate("x.NAME${}")));
      testOk1(pch && dbChannelFieldType(pch) == DBF_CHAR);
      testOk1(pch && dbChannelExportType(pch) == DBR_CHAR);
      testOk1(pch && dbChannelElements(pch) == PVNAME_STRINGSZ);
      if (pch) dbChannelDelete(pch);
  
      /* dbChannelCreate() rejects bad PVs */
      testOk(!dbChannelCreate("y"), "Create, bad record");
      testOk(!dbChannelCreate("x.NOFIELD"), "Create, bad field");
      testOk(!dbChannelCreate("x.{not-json}"), "Create, bad JSON");
      eltc(0);
      testOk(!dbChannelCreate("x.{\"none\":null}"), "Create, bad filter");
      eltc(1);
  
      dbRegisterFilter("any", &testIf, NULL);
  
      /* Parser event rejection by filter */
      e = e_start;
      testOk1(!dbChannelCreate("x.{\"any\":null}"));
  
      r = e_start;
      e = e_start | e_null | e_abort;
      testOk1(!dbChannelCreate("x.{\"any\":null}"));
  
      r = e_start | e_null;
      e = e_start | e_null | e_end;
      testOk1(!dbChannelCreate("x.{\"any\":null}"));
  
      /* Successful parsing... */
      r = r_any;
      e = e_start | e_null | e_end;
      testOk1(!!(pch = dbChannelCreate("x.{\"any\":null}")));
      e = e_close;
      if (pch) dbChannelDelete(pch);
  
      dbRegisterFilter("scalar", &testIf, NULL);
  
      e = e_start | e_null | e_end;
      testOk1(!!(pch = dbChannelCreate("x.{\"scalar\":null}")));
  
      e = e_report;
      dbChannelShow(pch, 2, 2);
  
      e = e_close;
      if (pch) dbChannelDelete(pch);
  
      e = e_start | e_start_array | e_boolean | e_integer | e_end_array
              | e_end;
      testOk1(!!(pch = dbChannelCreate("x.{\"any\":[true,1]}")));
      e = e_close;
      if (pch) dbChannelDelete(pch);
  
      e = e_start | e_start_map | e_map_key | e_double | e_string | e_end_map
              | e_end;
      testOk1(!!(pch = dbChannelCreate("x.{\"any\":{\"a\":2.7183,\"b\":\"c\"}}")));
      e = e_close;
      if (pch) dbChannelDelete(pch);
  
      /* More event rejection */
      r = r_scalar;
      e = e_start | e_start_array | e_abort;
      testOk1(!dbChannelCreate("x.{\"scalar\":[null]}"));
  
      e = e_start | e_start_map | e_abort;
      testOk1(!dbChannelCreate("x.{\"scalar\":{}}"));
  
      dbFreeBase(pdbbase);
      registryFree();
      pdbbase = NULL;
  
      return testDone();
  }
  