Skip to content
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

Fix error thrown when inserting keys #80

Merged
merged 5 commits into from
May 31, 2024

Conversation

kekavc24
Copy link
Contributor

Closes #69

  • Checks for ordering only when more than one key is present

  • I’ve reviewed the contributor guide and applied the relevant portions to this PR.
Contribution guidelines:

Note that many Dart repos have a weekly cadence for reviewing PRs - please allow for some latency before initial review feedback.

@kekavc24
Copy link
Contributor Author

kekavc24 commented May 29, 2024

@jonasfj Before I can add new test files. It seems many of the old tests were based on this bug e.g.

test('after multiple changes', () {
final yamlEditor = YamlEditor('YAML: YAML');
yamlEditor.update(['YAML'], "YAML Ain't Markup Language");
yamlEditor.update(['XML'], 'Extensible Markup Language');
yamlEditor.remove(['YAML']);
expect(yamlEditor.edits, [
SourceEdit(5, 5, " YAML Ain't Markup Language"),
SourceEdit(0, 0, 'XML: Extensible Markup Language\n'),
SourceEdit(32, 32, '')
]);
});

The fix now returns the new correct SourceEdit for second update as the correct offset for the edit 32 not 0 anymore. Should I update them?

@jonasfj
Copy link
Member

jonasfj commented May 29, 2024

By all means fix all the old tests too.

But are you sure wise to support reverse alphabetical ordering?
It might be unlikely that someone will do that intentionally.

Especially if there is only 2 properties then reverse alphabetical ordering is really just any order that isn't alphabetical :)

@kekavc24
Copy link
Contributor Author

kekavc24 commented May 29, 2024

Oh, yeah. Sorry for the overkill. I just thought it was a great addition. I'll fix that and the tests. Thanks.

@kekavc24
Copy link
Contributor Author

kekavc24 commented May 30, 2024

@jonasfj I'm still debugging the spliceList method. It seems something broke somewhere (or was manifested by the bug fix).

Copy link
Member

@jonasfj jonasfj left a comment

Choose a reason for hiding this comment

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

Other than tests still failing, I think this pretty good.

I could be convinced that we should sort new entries in maps that only contain a single key.
Given that we already have tests that rely on this behavior.

One could also argue that we should allow ordering to be configurable in YamlEditor -- but that's probably overkill. I wouldn't mind doing it, but if we want to pursue such things I'd suggest we do it in a separate PR.

lib/src/utils.dart Outdated Show resolved Hide resolved
Copy link
Member

Choose a reason for hiding this comment

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

This is happening because the ordering is changing, because this test starts with a map of size 1 and then just adds more keys. Which previously would be ordered, and now won't be.

hmm, I could see a point to ordering maps that have only 1 key. Since we apparently have a lot of tests doing exactly this.

@kekavc24 What do you think we should do? Should we order keys in a map when there is only 1 key?

I don't mind landing this as is.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think we should keep the changes. The tests now mirror expected behaviour rather than the bug.

Copy link
Member

Choose a reason for hiding this comment

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

But then we trigger the issue in random_test.dart, so we either have to add skip: true to random_test.dart so we can land this.

Or walk back the change with not sorting 1-key maps. Because then we don't trigger issues in random_test.dart.

We do want to fix the spliceList issue, but ideally in a different PR.


And I don't know what solution is best, building sorted maps when adding multiple keys to a map with a single key, feel equally good to just appending the keys.


TL;DR: Let's get the tests passing, so we can land. Either fixing spliceList, walking back the 1-key map change, or skipping tests (and later fixing spliceList).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Maybe skip? Once this is done, I'll start looking into that issue

Copy link
Member

Choose a reason for hiding this comment

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

Go for it... Add a skip thing and let's merge :)

test/update_test.dart Show resolved Hide resolved
@jonasfj
Copy link
Member

jonasfj commented May 30, 2024

By the way, the test that is failing is somewhere in test/random_test.dart.

I'd suggest trying to add more print statements and extract the YAML + the modification being made into a separate test.

Don't try to debug things in random_test.dart, try to extract the test case it generates instead. We'll want it as a hardcoded test case anyways :D

@jonasfj
Copy link
Member

jonasfj commented May 30, 2024

I noticed a few weird hacks with prints in random_test.dart and filed: #81

After that the failing test case should print:

Failed to call YamlModificationMethod.splice on:

environment: true


0.03167456183131434:
  - - true  
    - |-
        false
    - 4177511653
  - null
  - 2910742758
  - true
  - - false 
  - 2081477285
  - 1718060652
  - 0.9662696464087974
  - null
  - "9[hd24=s438Y7a8_!M&}s<3cTr{\"e%<1I7vq&xp4p\"9jH#NJv"
762977104: null

with the following arguments:
[0, 1, [0.7396543051030952, null, {P/wQ'@G`$A%6LA$aXk0Uy&f36Pi[~:905@`DJ~nKUs!2:$45hj[vk7V<Qt~\2M^Uk$, vZEjAW=tOT=Q&dpH,cN'hX$_wHR=:q?UvHzC;+8*bLX_5Rgvp)L{E, {null: null, {3368549847: null, 0.9104247116161583: dcN&=EiSL?{sWLMRg -W]%
[=~Ggy@Z9F<77:3dY!{wE;m\uC9R9S2sIv<LyIUK, JRBV\|1]O<K;Wosal33 f30@`?*{}?}ke>^Eg''oUXw+JXq%C|{Ck._BGk: true}: ヽ༼ຈل͜ຈ༽ノ ヽ༼ຈل͜ຈ༽ノ}, 1853891542]]
and path:   
[0.03167456183131434, 4]

Error Details:
Assertion failed: (package:yaml_edit) Modification did not result in expected result.

# YAML before edit:
>
> environment: true
>
>
> 0.03167456183131434:
>   - - true
>     - |-  
>         false
>     - 4177511653
>   - null  
>   - 2910742758
>   - true  
>   - - false
>   - 2081477285
>   - 1718060652
>   - 0.9662696464087974
>   - null
>   - "9[hd24=s438Y7a8_!M&}s<3cTr{\"e%<1I7vq&xp4p\"9jH#NJv"
> 762977104: null
>

# YAML after edit:
>
> environment: true
>
>
> 0.03167456183131434:
>   - - true
>     - |-
>         false
>     - 4177511653
>   - null
>   - 2910742758
>   - true
>     - 1853891542
>   - - false
>   - 2081477285
>   - 1718060652
>   - 0.9662696464087974
>   - null
>   - "9[hd24=s438Y7a8_!M&}s<3cTr{\"e%<1I7vq&xp4p\"9jH#NJv"
> 762977104: null
>

Please file an issue at:
https://github.com/dart-lang/yaml_edit/issues/new?labels=bug


#0      YamlEditor._performEdit (package:yaml_edit/src/editor.dart:579:7)
#1      YamlEditor.insertIntoList (package:yaml_edit/src/editor.dart:339:5)
#2      YamlEditor.spliceList (package:yaml_edit/src/editor.dart:376:7)
#3      _Generator.performNextModification (file:///workspace/yaml_edit/test/random_test.dart:209:20)
#4      main.<anonymous closure>.<anonymous closure> (file:///workspace/yaml_edit/test/random_test.dart:47:27)
#5      _ReturnsNormally.typedMatches (package:matcher/src/core_matchers.dart:153:8)
#6      FeatureMatcher.matches (package:matcher/src/feature_matcher.dart:16:42)
#7      _expect (package:matcher/src/expect/expect.dart:138:30)
#8      expect (package:matcher/src/expect/expect.dart:56:3)
#9      main.<anonymous closure> (file:///workspace/yaml_edit/test/random_test.dart:46:9)
#10     Declarer.test.<anonymous closure>.<anonymous closure> (package:test_api/src/backend/declarer.dart:215:19)
<asynchronous suspension>
#11     Declarer.test.<anonymous closure> (package:test_api/src/backend/declarer.dart:213:7)
<asynchronous suspension>
#12     Invoker._waitForOutstandingCallbacks.<anonymous closure> (package:test_api/src/backend/invoker.dart:258:9)
<asynchronous suspension>


00:03 +15 -1: testing with randomly generated modifications: test 15 [E]
  Expected: return normally
    Actual: <Closure: () => void>
     Which: threw _YamlAssertionError:<Assertion failed: (package:yaml_edit) Modification did not result in expected result.

            # YAML before edit:
            >
            > environment: true
            >
            >
            > 0.03167456183131434:
            >   - - true
            >     - |-
            >         false
            >     - 4177511653
            >   - null
            >   - 2910742758
            >   - true
            >   - - false
            >   - 2081477285
            >   - 1718060652
            >   - 0.9662696464087974
            >   - null
            >   - "9[hd24=s438Y7a8_!M&}s<3cTr{\"e%<1I7vq&xp4p\"9jH#NJv"
            > 762977104: null
            >

            # YAML after edit:
            >
            > environment: true
            >
            >
            > 0.03167456183131434:
            >   - - true
            >     - |-
            >         false
            >     - 4177511653
            >   - null
            >   - 2910742758
            >   - true
            >     - 1853891542
            >   - - false
            >   - 2081477285
            >   - 1718060652
            >   - 0.9662696464087974
            >   - null
            >   - "9[hd24=s438Y7a8_!M&}s<3cTr{\"e%<1I7vq&xp4p\"9jH#NJv"
            > 762977104: null
            >

            Please file an issue at:
            https://github.com/dart-lang/yaml_edit/issues/new?labels=bug
            >

  package:matcher             expect
  test/random_test.dart 46:9  main.<fn>

And from that we should be able to extract the test case.
Once extracted we can craft a minimal example from the test case.

I think it makes sense to have the failing test cases and minimum example as hardcoded test cases going forward.

random_test.dart does make consistent test cases, but the RNG might change in the future and then it'll test other things.


Side note: it's pretty cool that random_test.dart actually found an issue. It was really a shot in the dark when we made it 🤣 I had no idea that it would actually prove useful.

(Okay, we don't know that the issue it found is a useful error, but it hopefully is something worth fixing).

@jonasfj
Copy link
Member

jonasfj commented May 30, 2024

@kekavc24 I landed #81 I'd suggest rebasing this branch on main, then it'll be much easier to figure out what's wrong in random_test.dart.

@jonasfj jonasfj mentioned this pull request May 30, 2024
@jonasfj
Copy link
Member

jonasfj commented May 30, 2024

Also please do consider adding an entry to CHANGELOG.md.
Once we've landed this, I think we should cut a release, it's a pretty important bugfix!

I filed #82 to start the CHANGELOG.md.

@kekavc24
Copy link
Contributor Author

kekavc24 commented May 30, 2024

By the way, the test that is failing is somewhere in test/random_test.dart.

I'd suggest trying to add more print statements and extract the YAML + the modification being made into a separate test.

Don't try to debug things in random_test.dart, try to extract the test case it generates instead. We'll want it as a hardcoded test case anyways :D

@jonasfj I missed your comment. I hadn't refreshed the page 😅 or checked my email for notifications. I managed to find the issue by using the Zone (learned something new today 😅. I hadn't used it before!).

It seems YamlEditor uses the same strategy to append to map and list, that is, by looking for last \n character.

While this is great for a map and appending to the end of the list, for any other positions it may not be great. All methods to mutate a block list may depend on _insertInBlockList directly or indirectly if not inserting at the end.

/// Returns a [SourceEdit] describing the change to be made on [yamlEdit] to
/// achieve the effect of inserting [item] into [list] at [index], noting that
/// this is a block list.
///
/// [index] should be non-negative and less than or equal to `list.length`.
SourceEdit _insertInBlockList(
YamlEditor yamlEdit, YamlList list, int index, YamlNode item) {
RangeError.checkValueInInterval(index, 0, list.length);
if (index == list.length) return _appendToBlockList(yamlEdit, list, item);
final formattedValue = _formatNewBlock(yamlEdit, list, item);
final currNode = list.nodes[index];
final currNodeStart = currNode.span.start.offset;
final yaml = yamlEdit.toString();
final start = yaml.lastIndexOf('\n', currNodeStart) + 1;
return SourceEdit(start, 0, formattedValue);
}

It fails for block lists nested within another block list. Simple output :

0.03167456183131434:
  - true
  - - false

If we try to mutate via spliceList on the nested list with false at index 0 and delete it too,

# Insert test

# Results in 👇🏽 which throws an YamlAssertionError
0.03167456183131434:
  - true
  - test
  - - false

# Instead of 👇🏽
0.03167456183131434:
  - true
  - - test

My initial idea based on the existing _insertInBlockList code above,

  1. A block sequence element has a dash. So we always account for it as the offset isn't included. So, +1 if no space before value and +2 if there is a space.
  2. Scan for the indices of \n, - and ' ' (space). Why?
    • The greater the index, the closer it is. Thus, we will be able to compensate if we are inserting just before the first element of a nested block list.
    • The space just ensures we insert after if nested.

What do you think? Am I missing something?

@jonasfj
Copy link
Member

jonasfj commented May 30, 2024

hmm, I'm not sure I entirely follow.

But I have a few questions:

  • Is this only an issue given the bug fix we have in this PR?
    • I'm guessing this bug has been there a while, but that this PR just made it surface, right?
  • Are we only discovering the issue because of the bug fix we have in this PR?
    Because we no longer make 1 length maps sorted when inserting the second key.
    And this just happens to cause this bug to surface now.

I think if we can make test pass by sorting 1 length maps again, then we should do so.
AND then file an issue to fix the bug in spliceList. That way we can do it in a separate PR.


If on the other hand, the bug fix we made here, cause this bug in spliceList, then we should fix it in this PR.

@jonasfj
Copy link
Member

jonasfj commented May 30, 2024

I'm pretty sure this is an existing bug that just surfaced, try the test case.

SLICE LIST IN NESTED BLOCK LIST
---
key:
- true
- - false
---
- [splice, [key, 1], 0, 1, ['test']]

Just:

  • Create test/testdata/input/splicelist_in_nested_block_list.test with the contents above.
  • Run dart test test/golden_test.dart

It fails both on main and on this PR.


Question is why it only surfaces in random_test.dart in this PR.

@jonasfj
Copy link
Member

jonasfj commented May 30, 2024

If we switch the implementation to:

int getMapInsertionIndex(YamlMap map, Object newKey) {
  final keys = map.nodes.keys.map((k) => k.toString()).toList();

  // We can't deduce ordering if list is empty, so then we just we just append
  if (keys.isEmpty) {
    return map.length;
  }

  for (var i = 1; i < keys.length; i++) {
    if (keys[i].compareTo(keys[i - 1]) < 0) {
      return map.length;
    }
  }

  final insertionIndex =
      keys.indexWhere((key) => key.compareTo(newKey as String) > 0);

  if (insertionIndex != -1) return insertionIndex;

  return map.length;
}

Then we've still fixed the bug we set out to fix, right? @kekavc24
We'll be inserting in sorted order, even when there is only 1 entry in the map, but that's also how it worked before.

And with this random_test.dart just happens to pass. It's deterministically random, but it certainly doesn't try all combinations.


If we do that, then we've done the smallest possible change to fix the bug we're trying to fix. Then we can land this and move on the next bug. It's much easier if we don't let PRs grow too big.

I filed #83 for the issue with spliceList. And it'd be great if we can fix that too -- just easier to do in a separate PR.

@kekavc24
Copy link
Contributor Author

It's not from this fix.

@kekavc24 kekavc24 force-pushed the fix/invalid-key-insertion branch 3 times, most recently from 63c9528 to 9efed03 Compare May 30, 2024 15:18
@kekavc24
Copy link
Contributor Author

@jonasfj Wow, I'm sorry. I really fumbled this. I committed a file I was meant to delete.

My golden tests seem to be failing with no cause. Can you take a look?

@kekavc24 kekavc24 requested a review from jonasfj May 30, 2024 15:24
@jonasfj
Copy link
Member

jonasfj commented May 30, 2024

Make sure you delete the output files before running golden tests... Then review the newly generated output.

Otherwise, I can look tomorrow maybe.

@jonasfj jonasfj merged commit b582fd5 into dart-lang:main May 31, 2024
18 checks passed
@jonasfj
Copy link
Member

jonasfj commented May 31, 2024

@kekavc24 if you interested feel free to take stab at #83, or we can cut a release with these changes if we just want it out.

@kekavc24
Copy link
Contributor Author

Currently on it. @jonasfj

@kekavc24 kekavc24 deleted the fix/invalid-key-insertion branch May 31, 2024 11:28
copybara-service bot pushed a commit to dart-lang/sdk that referenced this pull request Jun 6, 2024
…annel, webdev, yaml_edit

Revisions updated by `dart tools/rev_sdk_deps.dart`.

crypto (https://github.com/dart-lang/crypto/compare/0a10171..7a9428a):
  7a9428a  2024-06-04  Kevin Moore  Bump lints dep (dart-lang/crypto#172)

dartdoc (https://github.com/dart-lang/dartdoc/compare/45627f9..ddb8fb4):
  ddb8fb44  2024-06-03  Konstantin Scheglov  Use package:analyzer/source/source.dart (dart-lang/dartdoc#3780)

http (https://github.com/dart-lang/http/compare/7bfbeea..a3567f8):
  a3567f8  2024-06-05  Brian Quinlan  Prepare to release cronet_http 1.3.1 (dart-lang/http#1228)
  7addc61  2024-06-05  Hossein Yousefi  [cronet_http] Upgrade jnigen to 0.9.2 to close `#1213` (dart-lang/http#1225)
  eb87a60  2024-06-04  Anikate De  pkgs/ok_http: Close the client (override `close()` from `BaseClient`) (dart-lang/http#1224)
  9f022d2  2024-06-04  Anikate De  pkgs/http_client_conformance_tests: add boolean flag `supportsFoldedHeaders` (dart-lang/http#1222)
  90837df  2024-06-03  Anikate De  pkgs/ok_http: Condense JNI Bindings to `single_file` structure, and add missing server errors test (dart-lang/http#1221)

logging (https://github.com/dart-lang/logging/compare/73f043a..dbd6829):
  dbd6829  2024-06-04  Sarah Zakarias  Update `topics` in `pubspec.yaml` (dart-lang/logging#165)

test (https://github.com/dart-lang/test/compare/2464ad5..83c597e):
  83c597e5  2024-06-05  Jacob MacDonald  enable asserts in dart2wasm tests (dart-lang/test#2241)
  60353804  2024-06-04  Nate Bosch  Remove an unnecessary `dynamic` (dart-lang/test#2239)
  43ad4cd8  2024-06-03  Kevin Moore  random cleanup (dart-lang/test#2232)
  18db77c3  2024-06-02  Kevin Moore  prepare to publish test_api (dart-lang/test#2238)
  cd72e8d8  2024-06-02  Kevin Moore  Prepare to publish (dart-lang/test#2237)
  9c6adaa7  2024-06-01  Kevin Moore  fixes (dart-lang/test#2235)
  0110a80b  2024-06-01  dependabot[bot]  Bump the github-actions group with 2 updates (dart-lang/test#2236)
  b1b2c029  2024-06-01  Martin Kustermann  Fix `dart run test -p chrome -c dart2wasm` (dart-lang/test#2233)

tools (https://github.com/dart-lang/tools/compare/86b3661..4321aec):
  4321aec  2024-06-05  Andrew Kolos  [unified_analytics] Suppress any `FileSystemException` thrown during `Analytics.send` (dart-lang/tools#274)
  e5d4c8b  2024-06-03  dependabot[bot]  Bump actions/checkout from 4.1.5 to 4.1.6 in the github-actions group (dart-lang/tools#271)

web_socket_channel (https://github.com/dart-lang/web_socket_channel/compare/45b8ce9..bf69990):
  bf69990  2024-06-03  Thibault Chatillon  bump web_socket dependency 0.1.3 -> 0.1.5 (dart-lang/web_socket_channel#370)
  5b428dd  2024-06-02  dependabot[bot]  Bump actions/checkout from 4.1.5 to 4.1.6 in the github-actions group (dart-lang/web_socket_channel#371)
  afd1e3c  2024-05-23  Brian Quinlan  Remove `--fatal-infos` from `dart pub downgrade` analysis (dart-lang/web_socket_channel#367)
  cb20b71  2024-05-23  Sarah Zakarias  Add `topics` to `pubspec.yaml` (dart-lang/web_socket_channel#362)
  8514229  2024-05-22  Kevin Moore  Bump and fix lints (dart-lang/web_socket_channel#366)

webdev (https://github.com/dart-lang/webdev/compare/a97c2a1..9ada46f):
  9ada46fc  2024-06-05  Nicholas Shahan  Hide more temporary variables when debugging (dart-lang/webdev#2445)

yaml_edit (https://github.com/dart-lang/yaml_edit/compare/963e7a3..08a146e):
  08a146e  2024-06-06  Kavisi  Fix splice list insertion (dart-lang/yaml_edit#84)
  561309e  2024-06-01  dependabot[bot]  Bump actions/checkout from 4.1.5 to 4.1.6 in the github-actions group (dart-lang/yaml_edit#85)
  b582fd5  2024-05-31  Kavisi  Fix error thrown when inserting keys (dart-lang/yaml_edit#80)

Change-Id: I36acbc26fe97d8a1e3fdfa5f4855cc4be7d84dd7
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/370080
Commit-Queue: Devon Carew <devoncarew@google.com>
Reviewed-by: Konstantin Shcheglov <scheglov@google.com>
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.

Duplicate mapping key and parsing error when trying to add a map entry
2 participants