#First assert always fails

29 messages · Page 1 of 1 (latest)

wooden hinge
#

test code: https://pastecord.com/ypykezamog.swift
regarding this code, the first assert

                So(assert.Eventually(
                    t,
                    func() bool {
                        return len(receiveCh) == 1
                    },
                    1500*time.Millisecond,
                    10*time.Millisecond,
                ),
                    ShouldBeTrue,
                )

Will fail each time despite the assert.Eventually
This is testing concurrent functions where h.Start() looks like this

func (h *hub[T]) Start() {
        for {
            select {
            case item, ok := <-h.sourceCh:
                if !ok {
                    panic("source channel was closed")
                }
                h.publishToSubscribers(item)
            case subscriber := <-h.subscribeCh:
                h.subscriptions[subscriber.id] = subscriber
            }
        }
}

Where h.publishToSubscribers(item) is this

go
func (h *hub[T]) publishToSubscribers(message T) {
    for _, sub := range h.subscriptions {
        select {
        case sub.receiveCh <- message:
        }
    }
}

Why does it fail despite giving it enough time for the go routine to read the message from source channel and write it to the receiveCh

pine moat
#

damn this testing framework makes it so difficult to follow whats happening

wooden hinge
#

Is testing goroutines and concurrent code meant to be such a pain in the ass?

velvet acorn
# wooden hinge Is testing goroutines and concurrent code meant to be such a pain in the ass?

Yesn't. It can be more difficult to test concurrent code, depending on what you're trying to test, but it's not inherently harder or easier to do so, it depends on your design and what (and how) you're trying to test.

I think your issues seem to be rooted in the fact that you don't fully grasp what's going on (that's ok! that's why help channels like this one exist).
I also think, though this is a mere observation, that the fact you are approaching testing in a non standard manner makes things more difficult for you and us.

Personally, every time you post this kind of test code, I have no idea how it works or what it's supposed to do. If I have limited time I can't really invest that into learning a new testing framework just to help you out.

I'm not discouraging you from using convey, just pointing out that the fact that you do may lower your chances of getting help since to do so would require the reader to also know what convey is and how it works.
Again that's not necessarily a reason not to use convey, just something to keep in mind. You can even generalize it to using any lib.

#

just to really drive in the point that I'm not telling you not to use convey: chi is also a library, however, since many people on the server use it, people who ask questions about chi tend to get effective and quick hhelp

burnt token
#

Maybe re-write the test using standard Go library and might get some help? I think your publisher will block if a subscriber is not blocked and waiting to read from the channel, no? I don't see anything reading the channel on the subscriber. Test code looks incomplete.

wooden hinge
#

@velvet acorn is there any resource that can help me understand what is exactly going on?

#

also Convey is something I have to use sadly

#

What I believe the issue is: By the time the first assert finishes the coroutine hasn't had the time to do it's action, hence it not writing the message to the receiveCH

#

but I don't know why since this is what the docs state for the Eventually
Eventually asserts that given condition will be met in waitFor time, periodically checking target function each tick.

velvet acorn
# wooden hinge <@516291054534787082> is there any resource that can help me understand what is ...

let's take it step by step: ```go

func TestHub(t *testing.T) {
// we create a channel with a buffer size of 1, this will matter later
msgs := make(chan interface{}, 1)
h := internal.NewHub(timeoutDuration, msgs, connectionChannelBufferSize)
_, receiveCh := h.Subscribe(receiveChannelBufferSize)

// we will now send a message.
// this cannot block by definition, since msgs has a buffer size of 1
msg := core.Message{
Data: "data",
}
msgs <- msg // can never block

// channels are a means for communication, so let's communicate!
// in particular we're interested that the subscriber receives the message we sent
// so let's see if the message we received is the message we sent
select {
case got := <-receiveCh:
if got != msg {
t.Error("...")
}
case <-time.After(a reasonable timeout):
t.Error("...")
}
}```

Channels are a means of synchronized communication.
In order for communication to succeed both sides must be ready. That is, a send on a channel will block until someone receives that value, and a receive will block until someone sends a value.

When a channel is buffered the sending rule changes a little bit, a send will block until there is sufficient space in the buffer.

In the example test I showed the send on msgs will never block, and we use select to receive a message from receiveCh up to "a reasonable timeout".

The select statement contains a list of "communications", 2 receives in our case.
It waits for any of its cases to be ready to proceed. In our case the receive from receiveCh is not ready to proceed until the hub sends that message. The timeout case is not ready to proceed until that much time has elapsed.
When a case is ready to proceed it gets picked and evaluated. If more than one cases are ready to communicate one will be picked at random.

#

There are 2 main differences from this code and yours:
1 - We're testing what we mean to test. That is, we're not interested at all in the buffer sizes of our channels, what we're interested in is that we read back what we sent.
2 - We're actually reading from receiveCh. Since we don't know how receiveCh gets created we can't know if it matters or not, but given your symptoms it probably does.
(3 - I find it much clearer😁)

#

oh I forgot to talk about this explicitly: go select { case sub.receiveCh <- message: }
using the rules I mentioned above, this is pointless and in all ways equivalent to just go sub.receiveCh <- message

#

taking what we learned above: go len(receiveCh) == 1 will only hold if and only if receiveCh has a buffer size > 0 and a message is sent to it and no one reads from it. So if the issue is not with how your test is setup (regarding convey/assert) then it's probably centered around your assumptions on how receiveCh is supposed to behave.

So, what's the value of receiveChannelBufferSize?

#

(note that my recommendation remains: you should test what you mean to test, testing behaviour through len is not the way to go. Just trying to answer your specific question)

wooden hinge
#

First of all, thank you for taking your time to write this out, it's not a small feat to compose this
I get what you're saying, I know the concept of channels and why they exist

#

Now you're gonna shoot me because I realised where my issue is

#

I forgot to initiate the hub itself

#
func (h *hub[T]) Start() {
        for {
            select {
            case item, ok := <-h.sourceCh:
                if !ok {
                    panic("source channel was closed")
                }
                h.publishToSubscribers(item)
            case subscriber := <-h.subscribeCh:
                h.subscriptions[subscriber.id] = subscriber
            }
        }
}
#

If you see the code in pastecord, you see that a go h.Start() is missing

#

so nothing was reading

#

hence the failing test

velvet acorn
#

😁 if you have the time I'd still consider refactoring the test to move away from len

wooden hinge
#

I will consider it

#

but first let me go and cry into a pillow for a while

#

since I lost around ~6 hours on this

#

thank you again, the explanation you pasted above is awesome @velvet acorn