Some initial comments Daphne Preston-Kendal (04 Apr 2026 10:01 UTC)
Re: Some initial comments Andrew Tropin (07 Apr 2026 11:29 UTC)
Re: Some initial comments Daphne Preston-Kendal (29 Jul 2026 20:17 UTC)
Re: Some initial comments Vincent Manis (he/him) (29 Jul 2026 21:03 UTC)
Re: Some initial comments Wolfgang Corcoran-Mathe (01 Aug 2026 16:16 UTC)
Re: Some initial comments Andrew Tropin (13 Aug 2026 06:12 UTC)
Re: Some initial comments Andrew Tropin (13 Aug 2026 06:03 UTC)
Re: Some initial comments Daphne Preston-Kendal (19 Aug 2026 06:49 UTC)
Re: Some initial comments Andrew Tropin (02 Sep 2026 12:17 UTC)
Re: Some initial comments Daphne Preston-Kendal (07 Sep 2026 08:18 UTC)
Re: Some initial comments Andrew Tropin (09 Sep 2026 11:04 UTC)

Re: Some initial comments Andrew Tropin 02 Sep 2026 12:16 UTC
On 2026-08-19 08:48, Daphne Preston-Kendal wrote:

> On 13 Aug 2026, at 08:02, Andrew Tropin <xxxxxx@trop.in> wrote:
>
>> On 2026-07-29 21:16, Daphne Preston-Kendal wrote:
>>
>>> 1. It is not possible to implement current-test-runner /
>>> set-current-test-runner! in portable Scheme
>>>
>>> In R7RS there is no way to set the value of a parameter object apart
>>> from its default value (given at make-parameter time) and local
>>> rebinding with parameterize. Either set-current-test-runner! cannot
>>> exist, or current-test-runner is not a parameter object.
>>>
>>> The recommendation ‘Libraries that provide a test runner should call
>>> set-current-test-runner! upon loading so that end users need not
>>> configure it manually’ which appears to be the raison d’être for
>>> set-current-test-runner! is ill advised in any case. Loading a library
>>> should not, in general, affect global state in a way that could lead
>>> to conflicts depending on the order of library instantiation, because
>>> the order of library instantiation is unspecified in Scheme.
>>>
>>> I would simply delete the set-current-test-runner! procedure.
>>
>> Hi Daphne!
>>
>> I think it's still implementable in r7rs:
>>
>> https://git.sr.ht/~abcdw/guile-ares-rs/commit/bfd84fa09a06f1dc884419e4187284fc2272b0ba
>
> Ah, interesting. This does imply somewhat different semantics than those which the current draft (and indeed the name set-current-test-runner!) seems to prescribe. If you call set-current-test-runner! in a block where current-test-runner is already set by parameterize, the set-current-test-runner! operation will have no effect until the dynamic extent of the parameterize block is left.
>
> That could be fixed by better specification (and ideally changing the name to ‘set-default-test-runner!’).

Good idea, actually my suitbl implementation was behaving in a slightly
different way (which is not what I was expecting and intended).

Yeah, set-default-test-runner! is a proper name here.  I updated the SRFI.

>
> But this doesn’t answer the problem that the recommendation which seems to justify the existence of ‘set-current-test-runner!’ is ill advised. To clarify: if I import multiple libraries that provide test runners (because, for example, I want to make it a run time option to my test suite which is used), and each of them calls set-current-test-runner! to establish its own runner as the default, then under Scheme’s semantics it is unspecified which of those calls to set-current-test-runner! will run last and thus ‘win’, or indeed whether any of the calls at all will run.

It's very unlikely case that multiple testing libraries will be loaded
at the same time and if so, it's a responsibility of the user to set a
proper default test runner.  I believe they will figure out how to setup
their REPL environment for this case.

The CLIs and IDE integrations will not rely on its value and will
provide their test runner no matter what is a default test runner.

>
> Using set-current-test-runner! in the recommended way will not work on Chez Scheme, for example. (Vide Zhu Zihao’s proposed erratum to the R6RS, which corrects an example which is broken for the same reason <https://codeberg.org/scheme/r6rs/issues/27>)
>
>> I've considered all those options and re-evaluated them again after your
>> message.
>>
>> 1. At the first glance, 'metadata is implementable in r7rs syntax-rules:
>> https://git.sr.ht/~abcdw/guile-ares-rs/tree/4d34ad857a7219476f6ff1bf9282da48d7c06851/item/src/guile/srfi/srfi-269-draft.scm#L112
>>
>> Not sure how "canonical" guile's syntax-rules is, but I hope it is.
>
> This is either not what is implied by the use of ‘quote’ in the
> defined syntax, or is wrong. If there is a local binding called
> ‘metadata’, then this will never match. You are depending on a
> semantic which is fickle and which really should not exist.

Yeah, I guess you are right.  That won't work with local binding.

This should do the trick:
--8<---------------cut here---------------start------------->8---
(define (metadata-marker? form-name marker-name)
  (and (eq? form-name 'quote)
       (eq? marker-name 'metadata)))

(define-syntax test
  (syntax-rules ()
    ((_ test-description (context)
        (form-name marker-name) metadata-value body body* ...)
     (if (metadata-marker? (quote form-name) (quote marker-name))
         (load-test test-description metadata-value
                    (lambda (context) body body* ...)
                    (body body* ...))
         (load-test test-description '()
                    (lambda (context)
                      (form-name marker-name) metadata-value body body* ...)
                    ((form-name marker-name) metadata-value body body* ...))))
    ((_ test-description ()
        (form-name marker-name) metadata-value body body* ...)
     (if (metadata-marker? (quote form-name) (quote marker-name))
         (load-test test-description metadata-value
                    (lambda (%test-context) body body* ...)
                    (body body* ...))
         (load-test test-description '()
                    (lambda (%test-context)
                      (form-name marker-name) metadata-value body body* ...)
                    ((form-name marker-name) metadata-value body body* ...))))
    ((_ test-description (context) body body* ...)
     (load-test test-description '()
                (lambda (context) body body* ...)
                (body body* ...)))
    ((_ test-description () body body* ...)
     (load-test test-description '()
                (lambda (%test-context) body body* ...)
                (body body* ...)))))
--8<---------------cut here---------------end--------------->8---

This implemementation looks uglier, but seems to work.

>
>> I consider the visual clarity is more important than general "idiomatic"
>> syntax, thus my final ranking of the options:
>>
>> 1.
>> 'metadata
>> `((a . ,b))
>>
>> 2.
>> (metadata
>> `((a . ,b)))
>>
>> 3. test-with-metadata/suite-with-metadata
>>
>> 'metadat is most clean and uncluttered and scales much better with the
>> amount of written tests
>
> This argument makes no sense. I appreciate that option 3 is worse from
> the point of view of future extensibility, but 1 and 2 are barely
> distinguishable.

--8<---------------cut here---------------start------------->8---
   (suite-loader "outer"       | (suite-loader "outer"
     'metadata                 |   (metadata
     '((shared . outer)        |    '((shared . outer)
       (outer? . #t))          |      (outer? . #t)))
                               |
     (suite "inner"            |   (suite "inner"
       'metadata               |     (metadata
       '((shared . inner)      |      '((shared . inner)
         (inner? . #t))        |        (inner? . #t)))
                               |
       (test "t1" ()           |     (test "t1" ()
         'metadata             |       (metadata
         '((shared . test)     |        '((shared . test)
           (test? . #t))       |          (test? . #t)))
                               |
         (is #t))))            |       (is #t))))
--8<---------------cut here---------------end--------------->8---

IMHO, skimming is harder, because parenthesis in second option places
the metadata expression on one more nestness level deeper. inner's
metadata and test are hard to quickly distinguish.  In the first option
it's clear the declaration part (description, context, metadata) is
finished, now the body of suite/test starts.

Also, it add confusion semantically-wise: is metadata a field of suite?
is test a field of suite? can it be multiple test declarations inside
the suite?

I wouldn't say it unbearable, but noticeable.

I have my doubts, but I'm still not convinced about switching to the
second one.

>
>> The primary difference between alists and records are open world
>> assumption vs closed world.
>
> No, records are also open world as long as you allow inheritance.
>
> There is no inheritance in R7RS small, admittedly, but R6RS has
3. Uniformity

Runtime test entity representation, metadata, fixture's and runner's
data will end up in the test context.  Having records and alists mixed
in the context makes it quite hard to work with it.

Alist are quite questionable in terms of ergonomics (clojure's core data
structure are much more fun to work with in this regard), but alists
still have a uniform access and manipulation API.
> inheritance, R7RS large will almost certainly have it, and every
> implementation in actual use I’m aware of has it in some form.
>
> The combination of a free ‘metadata’ field and inheritance would be
> sufficient. You also haven’t answered why metadata has to be an alist,
> which is probably a sharper point of criticism for me, being an
> unjustified restriction on users.

Metadata is obviously an alist, as it's unknown before the user decides
what he wants to attach to the test.

1. Consistency and reliability of implementation.

From my relatively short experience with Scheme the records are
completely screwed up.  There a dozens of records implementations with
variable compatibility.  Even in the context of one Scheme
implementation.

There is only one alist implementation in the average Scheme, AFAIK.

2. Uniformity.

Runtime test entity representation, metadata, fixture's and runner's
data will end up in the test context.  Having records and alists mixed
in the context makes it quite hard to work with it.

Alist are quite questionable in terms of ergonomics (pattern matching,
immutable operations, etc). (For example clojure's core data structures
are much more fun to work with in this regard). But alists still have a
relatively uniform access and manipulation API.

3. Accessibility.
To access fields in records you either have to import accessors
procedures or use "reflection" both of which are quite questionable in
terms of usability.

Imagine, to access some information inside test's context, you need to
know all the modules that contributed to the context and import all
symbols from them to access what is inside.  If you have a fixture
backing some record of database connection, you need to import this
fixture's modules. You want to access the test's compound metadata?
Export test runner module in which the compound metadata was calculated
and added to a new record.

Of course the question of ergonomics can be quite subjective, but from
my experience, using records with all their fancy macros-generated
accessors and stuff as data structures is a nightmare.  Even bigger
nightmare than not so ergonomic alists.

I considered using records here multiple times, but at the moment I have
almost no doubt that alists is the right tool for this job.

Thank you very much for the comments, they are really valuable and
helped to spot at least two significant bugs in the implementation.
Appreciate your time spent on this!

--
Best regards,
Andrew Tropin