refactor(test): provide means to validate metrics and observations#4671
refactor(test): provide means to validate metrics and observations#4671mkleczek wants to merge 1 commit intoPostgREST:mainfrom
Conversation
ab5ae74 to
bde5a2b
Compare
|
FYI I had a question about testing the order of the logs on #4668 (comment) |
@steve-chavez - #4668 (comment) (copying it here as well, I guess that's a better place to discuss it). That depends on how observations order is related to debug log entries order. They should be the same but we don't test it in HSpec tests - we only read observations from a Chan. This will skip SchemaCacheQueriedObs in between. If necessary we can implement non-lenient validation. |
26b8bb0 to
f57c9eb
Compare
c618304 to
366e3b9
Compare
| readUntil = void . untilM (pure . isJust . f) . readChan | ||
| loc = fold (head (prettySrcLoc . snd <$> getCallStack callStack)) | ||
| -- execute effectful computation until result meets provided condition | ||
| untilM cond m = fix $ \loop -> m >>= \a -> ifM (cond a) (pure a) loop |
There was a problem hiding this comment.
Is it the same as untilM? Perhaps we can reuse this package?
There was a problem hiding this comment.
Is it the same as untilM? Perhaps we can reuse this package?
Not sure if it is worth pulling in the whole package for a one-liner (we don't want npm story, I guess :-) ). Besides, monad-loops looks dormant...
There was a problem hiding this comment.
Not sure if it is worth pulling in the whole package for a one-liner [..] Besides, monad-loops looks dormant...
I think it would be worth it. monad-loops is a complete library for monadic iteration related functions. It looks dormant because I guess it is complete and bug free for years.
Besides, there are other handy functions in that library which could be useful in the future to improve our test infrastructure.
There was a problem hiding this comment.
Not sure if it is worth pulling in the whole package for a one-liner [..] Besides, monad-loops looks dormant...
I think it would be worth it.
monad-loopsis a complete library for monadic iteration related functions. It looks dormant because I guess it is complete and bug free for years.
I double-checked this library and it does not provide quite the function that we need here (ie. loop until the returned value meets provided criteria). I could reimplement the logic in terms of monad-loops untilJust function probably, but bear with me: pulling a library and reimplement larger function to adjust it to this library API, just to replace a one-liner, does not meet the bar to use the library IMHO.
Besides, there are other handy functions in that library which could be useful in the future to improve our test infrastructure.
Quite possibly you're right. But this is out of scope of this PR.
In general, importing a library "just in case it is needed in the future" is not a good idea IMO.
4d92fb1 to
443fb68
Compare
911e6e3 to
1cb184f
Compare
9cc8e04 to
d9c7cdf
Compare
3557af2 to
79f8228
Compare
|
@steve-chavez @taimoorzaeem do you think it is now in mergeable state? |
Almost there! I am quite sold on new interface for testing metrics and observation. However, the implementation part needs careful review because lots of new stuff there (async exceptions, masking/unmasking are new for me) and it's taking some more time from my side. |
b293a1a to
e2ad9f8
Compare
| -- helpers used to produce observation diagnostics in waitForObs | ||
| constrName :: (HasConstructor (Rep a), Generic a)=> a -> Text | ||
| constrName = genericConstrName . from | ||
|
|
||
| class HasConstructor f where | ||
| genericConstrName :: f x -> Text | ||
|
|
||
| instance HasConstructor f => HasConstructor (D1 c f) where | ||
| genericConstrName (M1 x) = genericConstrName x | ||
|
|
||
| instance (HasConstructor x, HasConstructor y) => HasConstructor (x :+: y) where | ||
| genericConstrName (L1 l) = genericConstrName l | ||
| genericConstrName (R1 r) = genericConstrName r | ||
|
|
||
| instance Constructor c => HasConstructor (C1 c f) where | ||
| genericConstrName = T.pack . conName |
There was a problem hiding this comment.
Hmm, is this strictly needed? Perhaps maybe we can just use the Show instance?
There was a problem hiding this comment.
Hmm, is this strictly needed? Perhaps maybe we can just use the
Showinstance?
@taimoorzaeem
Yes, it is needed: try to implement Show instance for Observation - I certainly tried that first :)
6aab215 to
419bfa8
Compare
| main :: IO () | ||
| main = do | ||
| poolChan <- newChan | ||
| -- make sure poolChan is not growing indefinitely | ||
| -- start a thread that drains the channel | ||
| -- this is necessary because test cases operate on | ||
| -- copies so poolChan is never read from | ||
| void $ forkIO $ forever $ readChan poolChan |
There was a problem hiding this comment.
Hmm, does this mean we have another thread running for the entire duration of postgrest-test-spec?
There was a problem hiding this comment.
Hmm, does this mean we have another thread running for the entire duration of
postgrest-test-spec?
Yes. Do you think it is a problem? Haskell threads are green threads so really lightweight.
29b27ec to
5956fa7
Compare
|
Thinking on a broader level, I think maybe we should separate observability tests from our spec test-suite. I have noticed that our spec tests mostly test end-to-end behaviour i.e request and response objects. Observability testing requires a different setup (threads, timeouts, state checking etc), so maybe we should give it it's own test-suite? Probably also move Pros:
Cons:
Not sure if it would be worth it, thoughts from team members? CC: @steve-chavez @wolfgangwalther @laurenceisla @mkleczek @develop7 |
I considered it both when implementing JwtCache tests and when working on this PR. First reason is, as you say:
IMHO splitting tests into separate Splitting into two separate suites (each with its own setup) would introduce a lot of redundancy but also lead to constant back-and-forth about the proper placement of a particular test (some tests are cross-cutting: require both e2e verification and state checking). |
Makes perfect sense, also considering that we'll need custom tooling for otel and for sentry.. adding those to (coverage setup is not that hard, an example here)
I think redundancy is fine if it makes the test suites more focused/isolated/reviewable, we already have some redundancy in io and spec schemas (sleep in test/io/fixtures/schema.sql, sleep in test/spec/fixtures/schema.sql) and that has been working well. |
OK, can it be done as a separate refactoring PR? This and stacked PRs can also wait for someone to contribute the new test suite structure. Maybe kaizen mindset would be a good idea? |
DISCLAIMER: This commit was authored entirely by a human without the assistance of LLMs. Some helpers are provided for introspecting metrics already (used in JWT cache tests). This change provides facilities to additionally validate emited Observation events. A new Spec module is also implemented, adding basic tests of schema cache reloading - their main goal is to excercise the new infrastructure.
5956fa7 to
67cc56c
Compare
Yes, agree. 👍
I will try to do it. Let's see. |
Cool, marking this one as draft and will rebase on top of the new structure. |
DISCLAIMER:
This commit was authored entirely by a human without the assistance of LLMs.Some helpers are provided for introspecting metrics already (used in JWT cache tests). This change provides facilities to additionally validate emited Observation events.
A new Spec module is also implemented, adding basic tests of schema cache reloading - their main goal is to excercise the new infrastructure.
This is a prerequisite for:
#4672 (and subsequent #4643)
#4668
#4645