-
Notifications
You must be signed in to change notification settings - Fork 30.5k
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
test: introduce tag functions to handle long strings in code #13016
Conversation
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.
Rubber stamp LGTM. This definitely makes texts like HTTP messages easier to read..
return expressions.reduce( | ||
(acc, expr, i) => `${acc}${expr}${strings[i + 1].replace(breaks, ' ')}`, | ||
strings[0].replace(breaks, ' ') | ||
).replace(/^ | $/g, ''); |
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.
.trim()
?
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.
On this stage, the replacing concerns the whole string, including interpolated variables, so I try to be extra careful here. i.e. to remove only two auxiliary spaces (which were framing line breaks before the reducing).
test/common/index.js
Outdated
|
||
// Removes two framing line breaks and only auxiliary indents | ||
exports.tagLF = function tagLF(strings, ...expressions) { | ||
const [, firstIndent = ''] = strings[0].match(/\n+( *)/) || []; |
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.
If I am reading correctly strings[0].match(/\n+( +)/)
should work as well?
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 case there are no indents — yes if I get it right, but maybe with *
this returns earlier, without using the fallbacks || []
and = ''
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.
@joyeecheung would fail for (badly formated) literals like:
const tagged = tagLF`
<!doctype html>
<html>
<head>
<meta charset="UTF-8">
<title></title>
</head>
<body>
${process.versions}
</body>
</html>
`;
would give firstIndent === undefined
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.
Great idea 👍
soft suggestion I tagged with [suggestion]
And I'm only rubber stamping test/parallel/test-promises-unhandled-rejections.js
'd0d1d2d3d4d5d6d7d8d9dadbdcdddedf' + | ||
'e0e1e2e3e4e5e6e7e8e9eaebecedeeef' + | ||
'f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff'); | ||
assert.strictEqual(hexStr, tagGlue` |
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.
IMHO looks better 👍
`${clientReceivedFIN} FIN received by client, ` + | ||
`${clientSentFIN} FIN sent by client, ` + | ||
`${serverConnections} FIN sent by server`); | ||
console.error(tagUnwrap` |
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 revert this file? it'll conflict with #13003
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.
OK.
@@ -1,5 +1,5 @@ | |||
'use strict'; |
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'm rubber stamping this file...
IMHO you're mixing adding the new tags with refactoring.
(Could move to another PR or a different commit)
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 have only reformatted the first string arguments in each function, nothing more. GitHub diff just goes astray here, sorry.
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'll take a look locally.
|
||
`; | ||
const sameHistoryFilePaths = tagLF` | ||
|
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.
why no indent?
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 will fix, thank you.
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 have recalled why: see the long line without a break in the previous variant, this is the only way to preserve 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.
[suggestion] I know you don't like to change too much, but I would go
const keepLF = '\n';
const fileName = path.join(common.tmpDir, '.node_repl_history');
const sameHistoryFilePaths = wrap`
${keepLF}
The old repl history file has the same name and location as the new one i.e.,
${fileName} and is empty.${keepLF}
Using it as is.${keepLF}
${keepLF}
`;
maybe even add export.keepLF = '\n'
to common
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 am afraid this would be complicated enough to scare out all other reviewers :)
JSON.stringify(testScript) + ' | ' + | ||
JSON.stringify(process.execPath) + ' ' + | ||
'-pe "process.stdin.on(\'data\' , () => process.exit(1))"'; | ||
const cmd = common.tagUnwrap` |
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.
Next PR suggestion: A tag with JSON.stringify
for j{}
variables?
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.
Or with util.inspect()
:)
test/common/README.md
Outdated
assert.strictEqual(unTagged, tagged); | ||
``` | ||
|
||
### tagCRLF() |
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.
[suggestion] maybe even rename tagHTTP
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 were other cases in the tests. And potentially can be others (Windows stuff etc).
test/common/index.js
Outdated
'as LOCALHOST or expect some tests to fail.'); | ||
console.error(exports.tagUnwrap` | ||
Looks like we're in a FreeBSD Jail. | ||
Please provide your default interface address |
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.
[suggestion] for better readability:
console.error(exports.tagUnwrap`
Looks like we're in a FreeBSD Jail.
Please provide your default interface address as LOCALHOST
or expect some tests to fail.
`);
|
||
// Removes all literal line breaks and leading spaces | ||
exports.tagGlue = function tagGlue(strings, ...expressions) { | ||
const breaks = /\n */g; |
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.
move all consts out of the functions. no need to redefine on each invocation.
could be:
const TAG_CONSTANTS = {
tagGlueBreaks: /\n */g,
tagUnwrapBreaks: /(\n *)+/g,
tagUnwarpTrimmer: /^ | $/g,
...
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 would prefer to keep the functions self-sufficient for better modularity while they are not used in libs critical for performance.
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 won't block on this, but you might find out you can reuse some, or other coders might reuse, also gives them better names.
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 also remember that common
module is considered too bloated, so I am afraid to pollute it with more global variables.
test/common/index.js
Outdated
|
||
// Removes two framing line breaks and only auxiliary indents | ||
exports.tagLF = function tagLF(strings, ...expressions) { | ||
const [, firstIndent = ''] = strings[0].match(/\n+( *)/) || []; |
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.
@joyeecheung would fail for (badly formated) literals like:
const tagged = tagLF`
<!doctype html>
<html>
<head>
<meta charset="UTF-8">
<title></title>
</head>
<body>
${process.versions}
</body>
</html>
`;
would give firstIndent === undefined
test/common/index.js
Outdated
// Removes two framing line breaks and only auxiliary indents | ||
exports.tagLF = function tagLF(strings, ...expressions) { | ||
const [, firstIndent = ''] = strings[0].match(/\n+( *)/) || []; | ||
const indent = new RegExp(`\n {0,${firstIndent.length}}`, 'g'); |
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.
[Suggestion]
IMHO go stricter, i.e. new RegExp(
\n {${firstIndent.length}}, 'g');
example:
const literlaHTML = tagLF`
<!doctype html>
<html>
<head>
<meta charset="UTF-8">
<script>
var x = [1,2,3].map(functor, seed)
.filter(predicate)
.reduce(functor, '');
<script>
<title></title>
</head>
<body>
${process.versions}
</body>
</html>
`;
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 would prefer to ignore such misindentation cases in favor of these cases:
{
const tagged = tagLF`
<!doctype html>
<html>
...
</html>
`;
}
NB: indentation before the last backtick is less than the first indentation. With your variant, it will be preserved with the trailing line break.
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.
BTW, these misindented fragments can be wrapped in ${''}
and they will be untouched.
Read your comments 👍 on all but #13016 (comment), think again, but it's your call. |
I've added a bit more explanations in the doc (including intro note) and addressed some requests. CI: https://ci.nodejs.org/job/node-test-pull-request/8070/ |
Start to alleviate multiline template literals manipulations.
I've replaced |
@refack I mean https://en.oxforddictionaries.com/definition/-fy Does it make sense? |
I'm just making faces |
cc @nodejs/collaborators : this PR extends test |
I am 👍 with the change, but 👎 on the amount of breakage that might introduce. I already had to backport a PR from master to v7 and soon v6, because of changes in the test harness. |
We keep adding more and more stuff to That said, I have no objections to these changes. |
@mcollina It is a pity how the backporting burden prevents us from lightening other burdens, but it seems we should respect this burden as we respect gravitation. @lpinca I understand and I recall the recent attempts to slim down the It seems we have sound (yet soft) objections against the both commits: the fist makes |
I'm in favor of this change, but only if it is backported asap to v6. Otherwise it creates too much churn. |
@mcollina I can do a backport PR for v6 after landing if this is not backported cleanly. How much asap this should be made? |
cc @nodejs/lts for that answer. |
@lpinca I agree that it is very important to lower the barrier (cognitive or other) for writing tests! But IMHO in this specific case the trade-off of cognitive load while writing code is cognitive load while reading/fixing code. There are good examples that definatly make the code much more readable, like here, and here @vsemozhetbyt will these tags benefit |
I have not checked yet, I just want to see if this can be accepted for tests. But I planned to try to apply for benchmarks as well. |
As for tradeoffs... For writing, the load is eased by optionality of using these functions. For reading, the load is increased by the "what the ... this |
@refack I'm a bit unconvinced by your second example. {
data: tagCRLFy`
POST / HTTP/1.0
Connection: keep-alive
`
} is actually harder for me to grok than {
data: 'POST / HTTP/1.0\r\n' +
'Connection: keep-alive\r\n' +
'\r\n'
} Anyway, to reiterate, I have nothing against these changes. I'm fine if this gets merged. |
fair enough... |
To follow up on #13016 (comment) IMHO since HTTP will probably be the main use case for this one, we could add (in following PR) a #{HTTP_SECTION_END} symbol. |
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 think I'm -1 on these changes for two reasons.
- It has become increasingly more difficult to cherry pick changes into the staging branches and I think this will continue to exacerbate the problem
- I find using a tag for this more difficult to immediately understand than using simple string concatenation. I feel like this adds more indirection.
@evanlucas I hear you. |
@vsemozhetbyt :] I definitely appreciate the effort put in |
A couple thoughts:
|
Well, it seems this is a too controversial proposition. So I better close for now to not distract more collaborators anymore. Thank you for all the feedback and time. I hope this was not completely in vain) |
@refack I shall ponder on this. |
Checklist
make -j4 test
(UNIX), orvcbuild test
(Windows) passesAffected core subsystem(s)
test
Multiline template literals in ES6 make long strings handling easier in code. However, there are many annoying nits that prevent this option from using in many cases: code block indentation adds unwanted spaces to these strings, leading and trailing backticks break alignment or add unwanted leading and trailing line breaks.
This PR proposes 4 helper functions to mitigate these difficulties:
common.tagGlue()
— joins a multiline template literal seamlessly, i.e. removes all literal line breaks and leading spaces (handy to code big text blobs not intended for human reading).common.tagUnwrap()
— joins a multiline template literal with a space, i.e. replaces all literal line breaks and leading spaces with a space, then trims one leading and one trailing space (handy to code long readable text lines).common.tagLFy()
— preserves line breaks as\n
, but removes two framing line breaks and only auxiliary indents (handy to code multiline readable text, JavaScript metacode, HTML markup etc).common.tagCRLFy()
— same astagLFy()
, but replaces all literal\n
by\r\n
(handy to code raw HTTP requests/responses).You can see examples in the proposed doc fragments.
This PR also demonstrates the applying these functions to many test fragments.
All these functions leave interpolated variables untouched, only literal parts are processed. In most cases, these functions get multiline strings with leading and trailing line breaks: this allows to divide code noise (backticks etc) and pure data. These leading and trailing line breaks are stripped by the tag functions. This should be taken into account: if the result needs a line break in the very end, an additional line break should be added (beware, for example, HTTP raw code — see samples in the second commit).
I understand that this is a big PR and mostly based on personal taste. So see it as a strawman and feel free to reject without thorough explanations.
Though the diff seems huge, the changes are mostly trivial, concern multiline blocks formatting and are easy to skim.