You can not select more than 25 topics Topics must start with a chinese character,a letter or number, can include dashes ('-') and can be up to 35 characters long.

regenerate.py 11 kB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351
  1. #! /usr/bin/env python3
  2. # This script regenerates TrustInSoft CI configuration.
  3. # Run from the root of the JSON-C project:
  4. # $ python3 trustinsoft/regenerate.py
  5. import re # sub
  6. import json # dumps, load
  7. import os # path, makedirs
  8. import binascii # hexlify
  9. import shutil # copyfileobj
  10. import glob # iglob
  11. import argparse # ArgumentParser, add_argument, parse_args
  12. # Outputting JSON.
  13. def string_of_json(obj):
  14. # Output standard pretty-printed JSON (RFC 7159) with 4-space indentation.
  15. s = json.dumps(obj, indent=4)
  16. # Sometimes we need to have multiple "include" fields in the outputted
  17. # JSON, which is unfortunately impossible in the internal python
  18. # representation (OK, it is technically possible, but too cumbersome to
  19. # bother implementing it here), so we can name these fields 'include_',
  20. # 'include__', etc, and they are all converted to 'include' before
  21. # outputting as JSON.
  22. s = re.sub(r'"include_+"', '"include"', s)
  23. return s
  24. # --------------------------------------------------------------------------- #
  25. # ----------------------------- PARSE ARGUMENTS ----------------------------- #
  26. # --------------------------------------------------------------------------- #
  27. parser = argparse.ArgumentParser(
  28. description="Regenerate the TrustInSoft CI files.",
  29. epilog="Please call this script only after building JSON-C.")
  30. parser.add_argument("build_dir", default="json-c-build",
  31. help="path to the build directory where cmake was called")
  32. parser.add_argument("--fuzz-tests", default="all",
  33. choices=["none", "some", "all"],
  34. help="include none / some / all fuzz tests in tis.config \
  35. (default is 'all')")
  36. parser.add_argument("--safe-arrays", action="store_true",
  37. help="for multidimensional arrays or arrays that are \
  38. fields inside structs, assume that accesses are in \
  39. bounds")
  40. args = parser.parse_args()
  41. fuzz_tests = args.fuzz_tests
  42. build_dir = args.build_dir
  43. safe_arrays = args.safe_arrays
  44. # Generated files which need to be a part of the repository.
  45. def make_simple_copy_file(filename):
  46. return (
  47. {
  48. "src": os.path.join(build_dir, filename),
  49. "dst": os.path.join("trustinsoft", "include", filename),
  50. }
  51. )
  52. files_to_copy = [
  53. make_simple_copy_file("apps_config.h"),
  54. make_simple_copy_file("config.h"),
  55. make_simple_copy_file("json_config.h"),
  56. ]
  57. # --------------------------------------------------------------------------- #
  58. # ---------------------------------- CHECKS --------------------------------- #
  59. # --------------------------------------------------------------------------- #
  60. def check_dir(dir):
  61. if os.path.isdir(dir):
  62. print(" > OK! Directory '%s' exists." % dir)
  63. else:
  64. exit("Directory '%s' not found." % dir)
  65. def check_file(file):
  66. if os.path.isfile(file):
  67. print(" > OK! File '%s' exists." % file)
  68. else:
  69. exit("File '%s' not found." % file)
  70. # Initial check.
  71. print("1. Check if all necessary directories and files exist...")
  72. check_dir("trustinsoft")
  73. check_dir(build_dir)
  74. for file in files_to_copy:
  75. check_file(file['src'])
  76. # --------------------------------------------------------------------------- #
  77. # -------------------- GENERATE trustinsoft/common.config ------------------- #
  78. # --------------------------------------------------------------------------- #
  79. common_config_path = os.path.join("trustinsoft", "common.config")
  80. def string_of_options(options):
  81. elts = []
  82. for opt_prefix in options: # e.g. opt_prefix == "-D"
  83. for opt_value in options[opt_prefix]: # e.g. opt_value == "HAVE_OPEN"
  84. elts.append(opt_prefix + opt_value) # e.g. "-DHAVE_OPEN"
  85. return " ".join(elts)
  86. def make_common_config():
  87. # C files.
  88. c_files = (
  89. sorted(glob.iglob("*.c", recursive=False)) +
  90. [ os.path.join("tests", "parse_flags.c") ]
  91. )
  92. # Compilation options.
  93. compilation_cmd = (
  94. {
  95. "-I": [
  96. "include",
  97. "..",
  98. os.path.join("..", "tests"),
  99. ],
  100. "-D": [],
  101. "-U": [],
  102. }
  103. )
  104. # Whole common.config JSON.
  105. config = (
  106. {
  107. "files": list(map(lambda file: os.path.join("..", file), c_files)),
  108. "compilation_cmd": string_of_options(compilation_cmd),
  109. "val-printf-singleton-pointers": True,
  110. "val-int-for-pointer-equality": [ "-1", "-2" ]
  111. }
  112. )
  113. # The "safe-arrays" field if not default (i.e. not true).
  114. if not safe_arrays:
  115. config["safe-arrays"] = False
  116. # Done.
  117. return config
  118. common_config = make_common_config()
  119. with open(common_config_path, "w") as file:
  120. print("2. Generate the 'trustinsoft/common.config' file.")
  121. file.write(string_of_json(common_config))
  122. # --------------------------------------------------------------------------- #
  123. # -------------------------------- tis.config ------------------------------- #
  124. # --------------------------------------------------------------------------- #
  125. tests = (
  126. [
  127. {
  128. "test": "test1",
  129. "formatted": False,
  130. },
  131. {
  132. "test": "test1",
  133. "formatted": True,
  134. "args": [ "plain" ],
  135. },
  136. {
  137. "test": "test1",
  138. "formatted": True,
  139. "args": [ "pretty" ],
  140. },
  141. {
  142. "test": "test1",
  143. "formatted": True,
  144. "args": [ "spaced" ],
  145. },
  146. {
  147. "test": "test1",
  148. "formatted": True,
  149. "args": [ "spaced", "pretty" ],
  150. },
  151. {
  152. "test": "test1",
  153. "formatted": True,
  154. "args": [ "spaced", "pretty", "pretty_tab" ],
  155. },
  156. {
  157. "test": "test2",
  158. "formatted": False,
  159. },
  160. {
  161. "test": "test2",
  162. "formatted": True,
  163. "args": [ "plain" ],
  164. },
  165. {
  166. "test": "test2",
  167. "formatted": True,
  168. "args": [ "pretty" ],
  169. },
  170. {
  171. "test": "test2",
  172. "formatted": True,
  173. "args": [ "spaced" ],
  174. },
  175. {
  176. "test": "test2",
  177. "formatted": True,
  178. "args": [ "spaced", "pretty" ],
  179. },
  180. {
  181. "test": "test2",
  182. "formatted": True,
  183. "args": [ "spaced", "pretty", "pretty_tab" ],
  184. },
  185. { "test": "test4" },
  186. { "test": "test_cast" },
  187. { "test": "test_charcase" },
  188. { "test": "test_compare" },
  189. { "test": "test_deep_copy" },
  190. { "test": "test_double_serializer" },
  191. { "test": "test_float" },
  192. { "test": "test_int_add" },
  193. { "test": "test_json_pointer" },
  194. { "test": "test_locale" },
  195. { "test": "test_null" },
  196. {
  197. "test": "test_object_iterator",
  198. "args": [ "." ],
  199. },
  200. { "test": "test_parse" },
  201. { "test": "test_parse_int64" },
  202. { "test": "test_printbuf" },
  203. { "test": "testReplaceExisting" },
  204. { "test": "test_set_serializer" },
  205. { "test": "test_set_value" },
  206. {
  207. "test": "test_util_file",
  208. "args": [ "." ],
  209. "filesystem": {
  210. "files": [
  211. {
  212. "from": "tests/valid.json",
  213. "name": "./valid.json"
  214. },
  215. {
  216. "from": "tests/valid_nested.json",
  217. "name": "./valid_nested.json"
  218. },
  219. {
  220. "name": "/dev/null"
  221. },
  222. ],
  223. },
  224. },
  225. { "test": "test_visit" },
  226. ]
  227. )
  228. fuzz_input_dir = os.path.join("trustinsoft", "fuzz_inputs")
  229. interesting_fuzz_inputs = frozenset([
  230. "0-10058b8cd9",
  231. "0-4735d351ed",
  232. "0-638577393e",
  233. "1-8e3702d59d",
  234. "1-fb0eb4ff8c",
  235. ])
  236. def is_fuzz_input_interesting(fuzz_input):
  237. if fuzz_tests == "all":
  238. return True
  239. elif fuzz_tests == "some":
  240. return fuzz_input in interesting_fuzz_inputs
  241. else:
  242. return False
  243. def fuzz_inputs():
  244. fuzz_filepaths = sorted(
  245. glob.iglob(os.path.join(fuzz_input_dir, "*.json"), recursive=False)
  246. )
  247. fuzz_inputs = (list(map(
  248. lambda path: (os.path.basename(path)).replace(".json", ""),
  249. fuzz_filepaths
  250. )))
  251. return list(filter(is_fuzz_input_interesting, fuzz_inputs))
  252. def make_test(test):
  253. name_elts = []
  254. if "formatted" in test:
  255. if test["formatted"]:
  256. name_elts.append("Formatted")
  257. if "args" in test:
  258. name_elts += test["args"]
  259. name = test["test"] + "_".join(name_elts)
  260. tis_test = (
  261. {
  262. "name": name,
  263. "include": common_config_path,
  264. }
  265. )
  266. if "formatted" in test:
  267. if test["formatted"]:
  268. compilation_cmd = { "-D": [ "TEST_FORMATTED" ] }
  269. else:
  270. compilation_cmd = { "-U": [ "TEST_FORMATTED" ] }
  271. tis_test["compilation_cmd"] = string_of_options(compilation_cmd)
  272. tis_test["files"] = [ os.path.join("tests", test["test"] + ".c") ]
  273. if "filesystem" in test:
  274. tis_test["filesystem"] = test["filesystem"]
  275. if "args" in test:
  276. tis_test["val-args"] = " " + " ".join(test["args"])
  277. return tis_test
  278. def make_fuzz_test(fuzz_input):
  279. fuzz_filename = fuzz_input + ".json"
  280. tis_test = (
  281. {
  282. "name": ("test_fuzz input " + fuzz_filename),
  283. "include": common_config_path,
  284. "files": [
  285. os.path.join("trustinsoft", "test_fuzz.c")
  286. ],
  287. "filesystem": {
  288. "files": [
  289. {
  290. "from": os.path.join(fuzz_input_dir, fuzz_filename),
  291. "name": "test.json"
  292. }
  293. ]
  294. },
  295. "val-args": " test.json"
  296. }
  297. )
  298. return tis_test
  299. tis_config = (
  300. list(map(make_test, tests)) +
  301. list(map(make_fuzz_test, fuzz_inputs()))
  302. )
  303. with open("tis.config", "w") as file:
  304. print("3. Generate the tis.config file.")
  305. file.write(string_of_json(tis_config))
  306. # --------------------------------------------------------------------------- #
  307. # ------------------------------ COPY .h FILES ------------------------------ #
  308. # --------------------------------------------------------------------------- #
  309. print("5. Copy generated files.")
  310. for file in files_to_copy:
  311. with open(file['src'], 'r') as f_src:
  312. os.makedirs(os.path.dirname(file['dst']), exist_ok=True)
  313. with open(file['dst'], 'w') as f_dst:
  314. print(" > Copy '%s' to '%s'." % (file['src'], file['dst']))
  315. shutil.copyfileobj(f_src, f_dst)