cborutil: implement sans I/O decoder
The vendored CBOR package decodes by calling read(n) on an object.
There are a number of disadvantages to this:
* Uses blocking I/O. If sufficient data is not available, the decoder
will hang until it is.
* No support for partial reads. If the read(n) returns less data than
requested, the decoder raises an error.
* Requires the use of a file like object. If the original data is in
say a buffer, we need to "cast" it to e.g. a BytesIO to appease the
decoder.
In addition, the vendored CBOR decoder doesn't provide flexibility
that we desire. Specifically:
* It buffers indefinite length bytestrings instead of streaming them.
* It doesn't allow limiting the set of types that can be decoded. This
property is useful when implementing a "hardened" decoder that is
less susceptible to abusive input.
* It doesn't provide sufficient "hook points" and introspection to
institute checks around behavior. These are useful for implementing
a "hardened" decoder.
This all adds up to a reasonable set of justifications for writing our
own decoder.
So, this commit implements our own CBOR decoder.
At the heart of the decoder is a function that decodes a single "item"
from a buffer. This item can be a complete simple value or a special
value, such as "start of array." Using this function, we can build a
decoder that effectively iterates over the stream of decoded items and
builds up higher-level values, such as arrays, maps, sets, and indefinite
length bytestrings. And we can do this without performing I/O in the
decoder itself.
The core of the sans I/O decoder will probably not be used directly.
Instead, it is expected that we'll build utility functions for invoking
the decoder given specific input types. This will allow extreme
flexibility in how data is delivered to the decoder.
I'm pretty happy with the state of the decoder modulo the TODO items
to track wanted features to help with a "hardened" decoder. The one
thing I could be convinced to change is the handling of semantic tags.
Since we only support a single semantic tag (sets), I thought it would
be easier to handle them inline in decodeitem(). This is simpler now.
But if we add support for other semantic tags, it will likely be easier
to move semantic tag handling outside of decodeitem(). But, properly
supporting semantic tags opens up a whole can of worms, as many
semantic tags imply new types. I'm optimistic we won't need these in
Mercurial. But who knows.
I'm also pretty happy with the test coverage. Writing comprehensive
tests for partial decoding did flush out a handful of bugs. One
general improvement to testing would be fuzz testing for partial
decoding. I may implement that later. I also anticipate switching the
wire protocol code to this new decoder will flush out any lingering
bugs.
Differential Revision: https://phab.mercurial-scm.org/D4414
#require fuzzywuzzy
$ cat >> $HGRCPATH << EOF
> [extensions]
> releasenotes=
> EOF
$ hg init simple-repo
$ cd simple-repo
A fix directive from commit message is added to release notes
$ touch fix1
$ hg -q commit -A -l - << EOF
> commit 1
>
> .. fix::
>
> Fix from commit message.
> EOF
$ cat >> $TESTTMP/single-fix-bullet << EOF
> Bug Fixes
> =========
>
> * Fix from release notes.
> EOF
$ hg releasenotes -r . $TESTTMP/single-fix-bullet
$ cat $TESTTMP/single-fix-bullet
Bug Fixes
=========
* Fix from release notes.
* Fix from commit message.
Processing again ignores the already added bullet.
$ hg releasenotes -r . $TESTTMP/single-fix-bullet
$ cat $TESTTMP/single-fix-bullet
Bug Fixes
=========
* Fix from release notes.
* Fix from commit message.
$ cd ..
Sections are unioned
$ hg init subsections
$ cd subsections
$ touch fix1
$ hg -q commit -A -l - << EOF
> Commit 1
>
> .. feature:: Commit Message Feature
>
> This describes a feature from a commit message.
> EOF
$ cat >> $TESTTMP/single-feature-section << EOF
> New Features
> ============
>
> Notes Feature
> -------------
>
> This describes a feature from a release notes file.
> EOF
$ hg releasenotes -r . $TESTTMP/single-feature-section
$ cat $TESTTMP/single-feature-section
New Features
============
Notes Feature
-------------
This describes a feature from a release notes file.
Commit Message Feature
----------------------
This describes a feature from a commit message.
Doing it again won't add another section
$ hg releasenotes -r . $TESTTMP/single-feature-section
Commit Message Feature already exists in feature section; ignoring
$ cat $TESTTMP/single-feature-section
New Features
============
Notes Feature
-------------
This describes a feature from a release notes file.
Commit Message Feature
----------------------
This describes a feature from a commit message.
$ cd ..
Bullets from rev merge with those from notes file.
$ hg init bullets
$ cd bullets
$ touch fix1
$ hg -q commit -A -l - << EOF
> commit 1
>
> .. fix::
>
> this is fix1.
> EOF
$ touch fix2
$ hg -q commit -A -l - << EOF
> commit 2
>
> .. fix::
>
> this is fix2.
> EOF
$ hg releasenotes -r 'all()' $TESTTMP/relnotes-bullet-problem
$ cat $TESTTMP/relnotes-bullet-problem
Bug Fixes
=========
* this is fix1.
* this is fix2.
$ touch fix3
$ hg -q commit -A -l - << EOF
> commit 3
>
> .. fix::
>
> this is fix3.
> EOF
$ hg releasenotes -r . $TESTTMP/relnotes-bullet-problem
$ cat $TESTTMP/relnotes-bullet-problem
Bug Fixes
=========
* this is fix1.
* this is fix2.
* this is fix3.
$ cd ..
Ignores commit messages containing issueNNNN based on issue number.
$ hg init simple-fuzzrepo
$ cd simple-fuzzrepo
$ touch fix1
$ hg -q commit -A -l - << EOF
> commit 1
>
> .. fix::
>
> Resolved issue4567.
> EOF
$ cat >> $TESTTMP/issue-number-notes << EOF
> Bug Fixes
> =========
>
> * Fixed issue1234 related to XYZ.
>
> * Fixed issue4567 related to ABC.
>
> * Fixed issue3986 related to PQR.
> EOF
$ hg releasenotes -r . $TESTTMP/issue-number-notes
"issue4567" already exists in notes; ignoring
$ cat $TESTTMP/issue-number-notes
Bug Fixes
=========
* Fixed issue1234 related to XYZ.
* Fixed issue4567 related to ABC.
* Fixed issue3986 related to PQR.
$ cd ..
Adds short commit messages (words < 10) without
comparison unless there is an exact match.
$ hg init tempdir
$ cd tempdir
$ touch feature1
$ hg -q commit -A -l - << EOF
> commit 1
>
> .. feature::
>
> Adds a new feature 1.
> EOF
$ hg releasenotes -r . $TESTTMP/short-sentence-notes
$ touch feature2
$ hg -q commit -A -l - << EOF
> commit 2
>
> .. feature::
>
> Adds a new feature 2.
> EOF
$ hg releasenotes -r . $TESTTMP/short-sentence-notes
$ cat $TESTTMP/short-sentence-notes
New Features
============
* Adds a new feature 1.
* Adds a new feature 2.
$ cd ..
Ignores commit messages based on fuzzy comparison.
$ hg init fuzznotes
$ cd fuzznotes
$ touch fix1
$ hg -q commit -A -l - << EOF
> commit 1
>
> .. fix::
>
> This is a fix with another line.
> And it is a big one.
> EOF
$ cat >> $TESTTMP/fuzz-ignore-notes << EOF
> Bug Fixes
> =========
>
> * Fixed issue4567 by improving X.
>
> * This is the first line. This is next line with one newline.
>
> This is another line written after two newlines. This is going to be a big one.
>
> * This fixes another problem.
> EOF
$ hg releasenotes -r . $TESTTMP/fuzz-ignore-notes
"This is a fix with another line. And it is a big one." already exists in notes file; ignoring
$ cat $TESTTMP/fuzz-ignore-notes
Bug Fixes
=========
* Fixed issue4567 by improving X.
* This is the first line. This is next line with one newline.
This is another line written after two newlines. This is going to be a big
one.
* This fixes another problem.