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.