hara.seedgen
Seed generation and xtalk test scaffolding.
hara.seedgen supports generated seed files and xtalk-oriented test scaffolding for language/runtime coverage.
1 Motivation
When many target languages need similar coverage, seed generation keeps test shape consistent while letting each language specialize emission and runtime details.
2 Walkthrough
2.1 Runtime language dispatch
Seed generation starts by normalising language identifiers. seedgen-normalize-runtime-lang turns keywords, symbols, and strings into the canonical runtime lang, while seedgen-dispatch-tag and seedgen-display-lang produce the surface syntax tag used in generated code.
normalise runtime language identifiers
(seedgen/seedgen-normalize-runtime-lang "js")
=> :js
(seedgen/seedgen-normalize-runtime-lang 'python)
=> :python
(seedgen/seedgen-dispatch-tag :js)
=> :js
(seedgen/seedgen-display-lang :python)
=> :python
look up the default runtime for a language
(seedgen/seedgen-default-runtime :js)
=> :basic
inspect the active dispatch map
(contains? (set (keys (seedgen/seedgen-dispatch-map))) "js")
=> true
2.2 Detecting runtime usage in forms
hara.seedgen inspects test forms to discover which runtimes a fact exercises. seedgen-dispatch-lang recognises !.lang calls, seedgen-runtime-reference-lang recognises runtime reference forms such as notify/wait-on, and seedgen-runtime-dispatch-langs collects every language found in an expression.
detect the runtime target of a form
(seedgen/seedgen-dispatch-lang '(!.js 1))
=> :js
(seedgen/seedgen-runtime-reference-lang '(notify/wait-on :lua 42))
=> :lua
(seedgen/seedgen-runtime-dispatch-langs '(+ (!.js 1) (!.lua 2)))
=> [:js :lua]
(seedgen/seedgen-form-lang '(!.js (+ 1 2)))
=> :js
collect languages across fact metadata
(seedgen/seedgen-coverage-langs
'^{:refer sample/foo :setup [(notify/wait-on :js 42)]}
(fact "x" (!.js 1) => 1))
=> [:js]
2.3 Seedgen configuration
Facts and scripts can carry :seedgen/base, :seedgen/lang, :seedgen/check, and :seedgen/root metadata. seedgen-base-config merges this configuration, seedgen-lang-entry selects the entry for a specific language, and seedgen-root-entry reads language-specific options from the root script metadata.
merge per-language configuration from metadata
(seedgen/seedgen-base-config
'^{:seedgen/base {:js {:suppress true} :all {:wrap true}}}
(!.js 1))
=> {:js {:suppress true} :all {:wrap true}}
select a language entry from merged config
(seedgen/seedgen-lang-entry
'^{:seedgen/base {:js {:suppress true} :all {:wrap true}}}
(!.js 1)
:js)
=> {:wrap true :suppress true}
read root script options for a language
(seedgen/seedgen-root-entry
'^{:seedgen/root {:all true :python {:runtime :basic}}}
(l/script- :js {:runtime :basic})
:python)
=> {:runtime :basic}
find suppressed languages
(seedgen/seedgen-suppressed-langs
'^{:seedgen/base {:js {:suppress true}}}
(!.js 1))
=> #{:js}
2.4 File-level helpers
At file scope, seedgen-skip? checks whether a namespace is opted out, seedgen-root-langs returns the root or derived runtime declarations, and seedgen-fact-forms collects facts keyed by their :refer symbol.
check whether a test file is skipped
(let [tmp (java.io.File/createTempFile "seedgen" ".clj")
path (.getAbsolutePath tmp)]
(try
(spit tmp "^{:seedgen/skip true}\n(ns sample.test\n (:use code.test)\n (:require [hara.lang :as l]))\n\n(l/script- :js {:runtime :basic})\n")
(seedgen/seedgen-skip? path)
(finally
(.delete tmp))))
=> true
read root and derived runtime declarations
(let [tmp (java.io.File/createTempFile "seedgen" ".clj")
path (.getAbsolutePath tmp)]
(try
(spit tmp "(ns sample.test\n (:use code.test)\n (:require [hara.lang :as l]))\n\n^{:seedgen/root {:all true}}\n(l/script- :js {:runtime :basic})\n\n(l/script- :lua {:runtime :basic})\n")
[(seedgen/seedgen-root-langs path true)
(seedgen/seedgen-root-langs path false)]
(finally
(.delete tmp))))
=> [[:js] [:lua]]
collect fact forms by their refer symbol
(let [tmp (java.io.File/createTempFile "seedgen" ".clj")
path (.getAbsolutePath tmp)]
(try
(spit tmp "(ns sample.test\n (:use code.test)\n (:require [hara.lang :as l]))\n\n^{:seedgen/root {:all true}}\n(l/script- :js {:runtime :basic})\n\n^{:refer sample/foo}\n(fact \"x\" (!.js 1) => 1)\n")
(keys (seedgen/seedgen-fact-forms path))
(finally
(.delete tmp))))
=> '[sample/foo]
3 API
4 xtbench authoring
The test-lang/xt suite is single-source coverage: a canonical JavaScript seed is generated into target-language xtbench files. Keep the seed and its facts intact so every target exercises the same behavior.
4.1 Target adapters without separate facts
When a capability needs a runtime-native adapter, keep the JS root and inject the target implementation through :seedgen/root :extra. Mark the JS adapter require with :seedgen/extra true, then use form-level :seedgen/base :transform metadata to replace only the adapter symbol. This preserves the fact, assertion, and control flow across languages.
4.2 Suppression is exceptional
Do not edit generated test-lang/xtbench files, remove the canonical :js seed, or add separate target-only facts merely to make a bench pass. Use :suppress only when the capability is genuinely unavailable on a target; record the reason and retain all portable coverage. Regenerate the affected bench and run both the seed and generated target test after every change.