-
Notifications
You must be signed in to change notification settings - Fork 127
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Share variable inspection logic between CDP and DAP #1001
base: master
Are you sure you want to change the base?
Conversation
lib/debug/server_dap.rb
Outdated
end | ||
|
||
vars = members.map { |member| variable(member.name, member.value) } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's nothing implemented like CDP's internalProperty
yet, but I think support for presentationHint
can be added here eventually to achieve a similar effect.
lib/debug/variable_inspector.rb
Outdated
MAX_LENGTH = 180 | ||
|
||
def value_inspect obj, short: true | ||
# TODO: max length should be configurable? | ||
str = DEBUGGER__.safe_inspect obj, short: short, max_length: MAX_LENGTH | ||
|
||
if str.encoding == Encoding::UTF_8 | ||
str.scrub | ||
else | ||
str.encode(Encoding::UTF_8, invalid: :replace, undef: :replace) | ||
end | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I just copied this snippet for now, but my eventual goal is to move all presentation logic out of ThreadClient
and into a shared class like this.
lib/debug/variable_inspector.rb
Outdated
def named_members_of obj | ||
members = case obj | ||
when Array then obj.map.with_index { |o, i| Member.new(name: i.to_s, value: o) } | ||
when Hash then obj.map { |k, v| Member.new(name: value_inspect(k), value: v) } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This used to be inconsistent before. VSCode would format Hash keys with value_inspect
, but not Chrome. Now they will both us it.
result += M_INSTANCE_VARIABLES.bind_call(obj).map{|iv| | ||
variable(iv, M_INSTANCE_VARIABLE_GET.bind_call(obj, iv)) | ||
} | ||
prop += [internalProperty('#class', M_CLASS.bind_call(obj))] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For Chrome, #class
is appended as the last prop, whereas for VSCode it was unshift
ed to the top.
1295842
to
6119aa8
Compare
34b1210
to
de6411b
Compare
obj = @var_map[vid] | ||
if obj |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This had a small bug, where true
would have its #class
expanded, but not false
or nil
:
Notice that false
and nil
still have disclosure triangles. This is because of another bug, in variable_
(lines 1001-1002), which assumed that all objects would have at least 1 named member (#class
), which wasn't the case here.
The new check fixes it:
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
... this change allocates one more Member than before:
#<Member name="#class" value=NilClass internal>
Which bumps some of the variablesReference
numbers in test/protocol/step_back_raw_dap_test.rb
by 1.
See 63517f5
result: result_variable.inspect_value, | ||
**render_variable(result_variable) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This result:
value used to come from the variable_
method when name
was nil
. That was pretty tricky, so I moved it here
lib/debug/server_dap.rb
Outdated
namedVariables += M_INSTANCE_VARIABLES.bind_call(obj).size | ||
|
||
if NaiveString === obj | ||
str = obj.str.dump | ||
vid = indexedVariables = namedVariables = 0 | ||
else | ||
str = value_inspect(obj) | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This just becomes a natural part of VariableInspecto#named_members_of
, so we don't have to worry about it here anymore.
str = value_inspect(obj) | ||
end | ||
|
||
if name |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This if name
check is really checking if this is going to be used in a VariablesResponse
(with a name:
). If name
is nil
, then this was called from #evaluate_result
for a EvaluateResponse
(with a result:)
.
I moved this logic to the evaluate
code path instead.
lib/debug/server_dap.rb
Outdated
variable[:indexedVariables] = indexedVariables unless indexedVariables == 0 | ||
variable[:namedVariables] = namedVariables unless namedVariables == 0 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is kind of nice, it declutters the response logging a lot, so it's easier to debug.
when String | ||
variable_ name, obj, namedVariables: 3 # #length, #encoding, #to_str |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This separation between the code that answers "what are the members?" and "how many members are there?" made it quite difficult to make changes to the presentation objects correctly.
Now both have a single source of truth, in VariableInspector
's #indexed_members_of
and #named_members_of
.
rescue Exception => e | ||
"<#inspect raises #{e.inspect}>" | ||
def self.safe_inspect obj, max_length: LimitedPP::SHORT_INSPECT_LENGTH, short: false | ||
LimitedPP.safe_inspect(obj, max_length: max_length, short: short) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I extracted this out so it can be used in VariableInspector
(without needing to require the whole session.rb
, such as in the unit test).
I left this stub to minimize the churn here
de6411b
to
ee133fd
Compare
ee133fd
to
fb05fe3
Compare
|
I've already run this with CDP, it works great.
@ko1 Done. I still call it |
module DEBUGGER__ | ||
class VariableInspector | ||
def indexed_members_of(obj, start:, count:) | ||
return [] if start > (obj.length - 1) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In this implementation, the #class
will disappear when displaying Array. Is this intentional behavior?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That makes sense (in the same spirit as #1004), though perhaps we should keep the #class
if it's a non-standard subclass of Array
. I'll make that in a separate PR.
Also, some tests are failed in protocol part. Can you fix them? |
@ono-max I can have a look over the holiday break next week :) |
a0a8b3b
to
2e850bc
Compare
Hey @ono-max, Happy new year! I've fixed the protocol tests, and rebased onto the latest master. There's 2 test failures in |
2e850bc
to
6837459
Compare
internalProperty('#begin', obj.begin), | ||
internalProperty('#end', obj.end), | ||
] | ||
if @obj_map.key?(oid) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch. I missed the edge case for falsy value.
def indexed_members_of(obj, start:, count:) | ||
return [] if start > (obj.length - 1) | ||
|
||
capped_count = [count, obj.length - start].min |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you add the comment why we need to compare count
and obj.length - start
and choose the minimum one? Sorry, I could not understand the reason.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's to keep the iteration from running off the end of the array. This used to give extra nil
s at the end. See 6837459
I can add a comment.
Could you please run the CI?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
It seems to be that some protocol tests are still failed. Can you please check them? If you need help, please let me know 👍 I believe that https://github.com/ruby/debug/actions/runs/7405610000/job/20183471986?pr=1001 and https://github.com/ruby/debug/actions/runs/7405610000/job/20183471690?pr=1001 are not flakey tests. |
@ono-max Hmmm I think we have one of those "it works on my machine" situations
I'm running on Ruby 3.3.0 on macOS Sonoma 14.2.1. I suspect these errors are coming from the ancestors chains being in different in CI, but I don't know why that would be. Could you please help me investigate this? I don't have access to a Linux machine to compare against. |
I investigated the cause of the failed tests. I believe that the reason is because ancestor classes are changed from Ruby 3.1. Thus, we can skip the test if the Ruby version is lower than 3.1, e.g. 6f154ac |
@ono-max Fantastic find! I pulled that commit into this PR, so you get credit for it :) Is this ready to merge? |
I appreciate your dedicated efforts and hard work. @ko1-san will merge this PR 👍 |
@ono-max Thanks! Could you run the CI one more time, so we're sure it's green before merging? |
@ono-max I think the remaining test failures are just flakey tests. I can reproduce them Ruby 3.1.4, but they fail less than 1/10 times. |
@ko1 Can you please review and merge this? |
These values are assumed to be 0 if they're missing.
6f154ac
to
48d487d
Compare
✅ All Tests passed!✖️no tests failed ✔️668 tests passed(2 flakes) |
@ko1 Are these flaky tests? I don't understand what went wrong, but it doesn't seem related to these changes |
The logic that decides how variables are presented is repeated between the CDP and DAP implementations. This is error prone and leads to inconsistencies, where improvements are made to one or the other, but not both. E.g.:
#dump
member forString
only exists in VSCode DAP: show#dump
when pp'ed string is too long #869#class
members only exists for VSCode DAP: add the ability to hide the #class item for each object #986value_inspect
, but not Chrome#class
appears at the end, whereas DAP adds#class
to the start.This PR extracts the variable inspection logic into a new
VariableInspector
class, which can be shared between VSCode and Chrome. This gives a central place to make future improvements.I tried to make only the minimal behaviour changes necessary to unify the two implementations.
This PR then unlocks the ability to make some changes really nice/easily:
:sym: "value"
→sym: "value"
) #1003#class
for simple values #1004