Skip to content

Conversation

@HaraldNordgren
Copy link
Contributor

@HaraldNordgren HaraldNordgren commented Oct 25, 2025

Fixes #1813.

@olitez
Copy link

olitez commented Oct 25, 2025

hi all,

not showing actual diff, but no panic so it's looks good for me

        	Error:      	Not equal:
        	            	expected: query.test{m:map[[1]uint8]string{[1]uint8{0x1}:"one", [1]uint8{0x2}:"two"}}
        	            	actual  : query.test{m:map[[1]uint8]string(nil)}
        	            	
        	            	Diff:
        	Test:       	TestXxx

@HaraldNordgren
Copy link
Contributor Author

Ping @dolmen @brackendawson @ccoVeille

@ccoVeille
Copy link
Collaborator

I feel like the test you could cover more use cases.

Let me try yo understand and rephrase what this PR is trying to fix:

If we refers to the issue, the code was panicking when the struct had no exported fields, there is a map with at least 2 values.

So here we replace the go-spew diffs, by something else. But as reported this "something" is not a diff, but something else.

the fact go-spew could panic is random. Here you identify an issue but there could be others.

I wonder whether having a defer recover with error management to go-spew couldn't help.

The fact the diff is broken shouldn't break the assertion lib. It should report the test is falling, and here you could try to render things with the solution you found.

I'm not saying your way is wrong, not my solution is a good one. I'm asking for feedbacks.

I'm a bit afraid we are replacing a diff that works in many cases except a few random ones. When one of them was clearly identified, while others may stay hidden in the darkness able to panic.

Also, I'm a bit afraid, the code added could lead to a placeholder reporting that cannot compete with the diffs available in go-spew, when it might not fail in some edge cases we didn't identified yet.

Again, I'm not asking for code changes, but for an open discussion.

I have no problem in being wrong 😁

@HaraldNordgren
Copy link
Contributor Author

It's a valid question for sure and you have a good point that this is just one cause of potentially many others.

But using recover as a strategy feels wrong. Maybe it's just my feeling.

@ccoVeille
Copy link
Collaborator

@brackendawson do you remember the discussion we had about importing the code of go-spew, the same way we imported difflib ?

The conclusion was not yet, because we don't know yet the "unforseen consequences" of importing the code of go-difflib, so we don't have enough information and experience on this.

Maybe it's time to reconsider it.

Importing the code of go-spew diff and and fixing the bug in the code could be considered.

@ccoVeille
Copy link
Collaborator

Based on go-spew code

This is what's panic is the call to reflect.Value.Interface

https://cs.opensource.google/go/go/+/refs/tags/go1.25.4:src/reflect/value.go;drc=f1e6ae2f6f5424f9f5c6dc915866d4f457c1483e;l=1484

So the fix would be about calling reflect.Value.CanInterface

https://cs.opensource.google/go/go/+/refs/tags/go1.25.4:src/reflect/value.go;drc=f1e6ae2f6f5424f9f5c6dc915866d4f457c1483e;l=1457

Especially if we consider the comment

CanInterface reports whether Value.Interface can be used without panicking.

So the fix would be about adding calls to reflect.Value.CanInterface in this for loop before calling reflect.Value.Interface

https://github.com/davecgh/go-spew/blob/8991bc29aa16c548c550c7ff78260e27b9ab7c73/spew/common.go#L315

Comment on lines +1931 to +1946
func isStructWithUnexportedMapField(t reflect.Type) bool {
if t.Kind() != reflect.Struct {
return false
}

for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
if !field.IsExported() &&
field.Type.Kind() == reflect.Map &&
field.Type.Key().Kind() == reflect.Array {
return true
}
}

return false
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Based on the code the discussion that happened here

And based on the code of go-spew

#1816 (comment)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like started writting something, but I posted before putting all together.

So here, I was spotting the code here limited to unexported field that are maps with 2 elements, but go-spew code would panic when dealing with Interface and here, maybe a better fix would be to use CanInterface and check nothing else.

I'm unsure what other code examples could panic, but maybe it could be considered as a better approach than the "recover" I suggested.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel this might not be enough.

I checked at go-spew code while working on https://github.com/stretchr/testify/pull/1824/files

I feel like a code like this won't be caught by your fix

type Bar struct {
	  m map[[1]byte]*struct{}
}

type Foo struct {
  Foo Bar
}

When using a Foo, the problem is wider that simply recursively parsing the fields, as you need to also go through the whole stack, check for the pointer, the slices, maps, and arrays

So have something that goes as deep as go-spew.

@ccoVeille
Copy link
Collaborator

It's a valid question for sure and you have a good point that this is just one cause of potentially many others.

But using recover as a strategy feels wrong. Maybe it's just my feeling.

You are right, considering catching the panic with recover and then try to apply the patch is not a good solution.

I provided you some feedback and suggestion to your PR, please take a look at them.

But I feel like the recover idea should be considered anyway.

The fact the diff can panic is bad.
Your idea to add condition to avoid the panic that was identified is good.

But maybe we could also add a recover to invalidate the diff from error reporting when it panic, instead if breaking everything.

@HaraldNordgren
Copy link
Contributor Author

HaraldNordgren commented Nov 6, 2025

@ccoVeille Thanks for the ideas, but I'm not exactly sure which of these ideas you want me to implement. All of them or only a few?

I'm not exactly sure how to properly use CanInterface, I tried a few things and it panicked 🤗

@ccoVeille
Copy link
Collaborator

@ccoVeille Thanks for the ideas, but I'm not exactly sure which of these ideas you want me to implement. All of them or only a few?

I'm sorry about that. I should have been clearer.

Many of the point I raised are open for debate with everyone, including you, and also other maintainers.

We can discuss about them, some of them or all the ideas I raised might be rejected. I just shared my thoughts about the possible resolutions. If we decide to go in that direction new PR will be opened I think.

Right now, I'm good with your changes. That's why I approve your PR.

I'm not exactly sure how to properly use CanInterface, I tried a few things at it panicked 🤗

The CanInterface ideas were for fixing the bug in go-spew code.

@HaraldNordgren
Copy link
Contributor Author

HaraldNordgren commented Nov 7, 2025

@ccoVeille Got it!

Are there any plans to fix go-spew? I guess it would need to be by forking (since @davecgh seems to have abandoned it), which then in practice introduces a new transitive dependency for all testify users.

@HaraldNordgren
Copy link
Contributor Author

@ccoVeille Let's merge this?

@ccoVeille
Copy link
Collaborator

I was waiting reaction from other maintainers

@brackendawson
Copy link
Collaborator

This only fixes a narrow set of cases where spew may try to derive a value from an un-exported field, right? I think we should either avoid calling spew in all such cases or we should look at fixing 'spew. Fixing spew might mean moving it into this repository.

Not really a fan of just merging this for now, I believe it's an incomplete fix and is not urgent, this bug has been there for a long time.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

assert.Equal panic on diff unexported map with array as key

4 participants