Skip to content

Fix Nor 3/4-arity, timer state ordering, empty And/Or and timespan validation - #35

Merged
DevJasperNL merged 1 commit into
mainfrom
bugfix/nor-arity-timer-state-and-timespan-validation
Sep 5, 2026
Merged

Fix Nor 3/4-arity, timer state ordering, empty And/Or and timespan validation#35
DevJasperNL merged 1 commit into
mainfrom
bugfix/nor-arity-timer-state-and-timespan-validation

Conversation

@DevJasperNL

Copy link
Copy Markdown
Owner

Summary

Functional bug fixes found in a full review of the library, plus the regression tests that were missing for each of them.

Bugs fixed

Nor with three or four inputs returned OR (BooleanObservableExtensions.Operators.Or.cs)
The 3- and 4-arity Nor overloads were missing the trailing .Not(). With all inputs false they emitted false instead of true. There were no Nor tests at all, which is how it slipped through. Both overloads now delegate to Or(...).Not() exactly like Nand.

Timer and re-entrancy state corruption (TimedBooleanOperator.cs)
LastSourceValue was assigned after the subclass hook ran. Two concrete failures:

  • WhenTrueFor(span, Scheduler.Immediate) threw NullReferenceException on the first true, because the immediate scheduler runs the timer inline while LastSourceValue is still null.
  • An observer that feeds back into the source (same-thread re-entrancy passes the lock) had its value clobbered when the outer call resumed. With WhenStableFor, a fed-back false was silently lost and the output stayed true.

The value is now recorded before dispatching and the previous value is handed to OnSourceValue(value, previous) explicitly. Unsubscribe also flips the terminated flag before the timer is disposed.

Empty and null inputs to And / Or
CombineLatest over zero sources never emits and never completes, so Array.Empty<IObservable<bool>>().And() was a silent Never. An empty collection now emits true for And and false for Or and completes. A null observable or a null element throws an argument exception at call time instead of a NullReferenceException at subscribe time.

Publish workflow used the wrong tag (.github/workflows/nuget-publish.yml)
The version came from the newest tag in the repository rather than the release that triggered the run, so a patch release on an older line would have been stamped with the wrong version. It now uses github.event.release.tag_name.

Breaking change

Zero or negative timespans now throw ArgumentOutOfRangeException in every scheduling operator, matching what BlinkWhileTrue already did. Previously TrueForAtLeast, PersistTrueFor, WhenTrueFor, WhenStableFor and PulseTrueFor returned the raw source for timeSpan <= Zero, which bypassed distinctUntilChanged and WhenTrueFor's "first output is always false" contract, and LimitTrueDuration accepted any value and silently forced false right after every true for a negative span. The XML docs and the README (Common options) document the new behaviour.

Tests

  • Nand / Nor truth tables for every overload (2-, 3-, 4-arity, params, IEnumerable, collection) over all 16 input combinations
  • Re-entrant feedback with WhenStableFor
  • WhenTrueFor with Scheduler.Immediate
  • Empty collection and null inputs for And / Or
  • Zero and negative timespan per scheduling operator

All five new behavioural tests fail against the previous library code and pass with this change. Full suite: 619 tests green on net8.0, net9.0 and net10.0, built with TreatWarningsAsErrors=true.

…lidation

- Nor with three or four inputs returned OR because the trailing Not() was
  missing; both overloads now mirror Nand.
- TimedBooleanOperator recorded LastSourceValue after dispatching to the
  subclass, so a timer that fires synchronously (Scheduler.Immediate) threw
  NullReferenceException and a value fed back re-entrantly from the observer
  was overwritten and lost. The value is now recorded first and the previous
  value is passed to OnSourceValue explicitly. Unsubscribe also sets the
  terminated flag before the timer is torn down.
- All scheduling operators throw ArgumentOutOfRangeException for a zero or
  negative timespan, matching BlinkWhileTrue. Previously five of them returned
  the raw source (bypassing distinctUntilChanged and WhenTrueFor's initial
  false) and LimitTrueDuration accepted any value.
- And/Or over an empty collection emit true/false and complete instead of
  never signalling; null observables and null elements throw at call time.
- nuget-publish derives the version from the release that triggered the run
  instead of the newest tag in the repository.

Adds regression tests for every fix: Nand/Nor truth tables across all
overloads, re-entrant feedback, Scheduler.Immediate, empty and null inputs,
and zero/negative timespans per operator.
@DevJasperNL
DevJasperNL merged commit a901fc4 into main Sep 5, 2026
4 checks passed
@DevJasperNL
DevJasperNL deleted the bugfix/nor-arity-timer-state-and-timespan-validation branch September 5, 2026 13:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant