Skip to content

Commit 6829805

Browse files
control log levels and filter tests by file and description
- add summary stats for parser generation - add a perf counter for grammar build time - add negative regex matching - move Console to build-support - move PatternSet to build-support
1 parent 817c39a commit 6829805

File tree

3 files changed

+189
-7
lines changed

3 files changed

+189
-7
lines changed

Cakefile

Lines changed: 76 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,18 @@
11
fs = require 'fs'
22
os = require 'os'
33
path = require 'path'
4+
{ performance } = require 'perf_hooks'
45
_ = require 'underscore'
56
{ spawn, exec, execSync } = require 'child_process'
67
CoffeeScript = require './lib/coffeescript'
78
helpers = require './lib/coffeescript/helpers'
9+
{ setupConsole } = require './build-support/console'
10+
{ PatternSet } = require './build-support/patterns'
811

912
# ANSI Terminal Colors.
1013
bold = red = green = yellow = reset = ''
11-
unless process.env.NODE_DISABLE_COLORS
14+
USE_COLORS = process.stdout.hasColors?() and not process.env.NODE_DISABLE_COLORS
15+
if USE_COLORS
1216
bold = '\x1B[0;1m'
1317
red = '\x1B[0;31m'
1418
green = '\x1B[0;32m'
@@ -29,6 +33,12 @@ header = """
2933
# Used in folder names like `docs/v1`.
3034
majorVersion = parseInt CoffeeScript.VERSION.split('.')[0], 10
3135

36+
option '-l', '--level [LEVEL]', 'log level [debug < info < log(default) < warn < error]'
37+
38+
task = (name, description, action) ->
39+
global.task name, description, ({level = 'log', ...opts} = {}) ->
40+
setupConsole {level, useColors: USE_COLORS}
41+
action {...opts}
3242

3343
# Log a message with a color.
3444
log = (message, color, explanation) ->
@@ -53,13 +63,32 @@ run = (args, callback) ->
5363
buildParser = ->
5464
helpers.extend global, require 'util'
5565
require 'jison'
66+
67+
startParserBuild = performance.now()
68+
69+
# Gather summary statistics about the grammar.
70+
parser = require('./lib/coffeescript/grammar').parser
71+
{symbols_, terminals_, productions_} = parser
72+
countKeys = (obj) -> (Object.keys obj).length
73+
numSyms = countKeys symbols_
74+
numTerms = countKeys terminals_
75+
numProds = countKeys productions_
76+
console.info "parser created (#{numSyms} symbols, #{numTerms} terminals, #{numProds} productions)"
77+
78+
loadGrammar = performance.now()
79+
console.info "loading grammar: #{loadGrammar - startParserBuild} ms"
80+
5681
# We don't need `moduleMain`, since the parser is unlikely to be run standalone.
57-
parser = require('./lib/coffeescript/grammar').parser.generate(moduleMain: ->)
58-
fs.writeFileSync 'lib/coffeescript/parser.js', parser
82+
fs.writeFileSync 'lib/coffeescript/parser.js', parser.generate(moduleMain: ->)
83+
84+
parserBuildComplete = performance.now()
85+
console.info "parser generation: #{parserBuildComplete - loadGrammar} ms"
86+
console.info "full parser build time: #{parserBuildComplete - startParserBuild} ms"
5987

6088
buildExceptParser = (callback) ->
6189
files = fs.readdirSync 'src'
6290
files = ('src/' + file for file in files when file.match(/\.(lit)?coffee$/))
91+
console.dir.debug {files}
6392
run ['-c', '-o', 'lib/coffeescript'].concat(files), callback
6493

6594
build = (callback) ->
@@ -401,15 +430,24 @@ task 'bench', 'quick benchmark of compilation time', ->
401430

402431

403432
# Run the CoffeeScript test suite.
404-
runTests = (CoffeeScript) ->
433+
runTests = (CoffeeScript, {filePatterns, negFilePatterns, descPatterns, negDescPatterns} = {}) ->
405434
CoffeeScript.register() unless global.testingBrowser
406435

436+
filePatterns ?= PatternSet.empty()
437+
negFilePatterns ?= PatternSet.empty {negated: yes}
438+
descPatterns ?= PatternSet.empty()
439+
negDescPatterns ?= PatternSet.empty {negated: yes}
440+
console.dir.debug {filePatterns, negFilePatterns, descPatterns, negDescPatterns}
441+
407442
# These are attached to `global` so that they’re accessible from within
408443
# `test/async.coffee`, which has an async-capable version of
409444
# `global.test`.
410445
global.currentFile = null
411446
global.passedTests = 0
412447
global.failures = []
448+
global.filteredOut =
449+
files: []
450+
tests: []
413451

414452
global[name] = func for name, func of require 'assert'
415453

@@ -429,9 +467,22 @@ runTests = (CoffeeScript) ->
429467
error: err
430468
description: description
431469
source: fn.toString() if fn.toString?
470+
onFilteredOut = (description, fn) ->
471+
console.warn "test '#{description}' was filtered out by patterns"
472+
filteredOut.tests.push
473+
filename: global.currentFile
474+
description: description
475+
fn: fn
476+
onFilteredFile = (file) ->
477+
console.warn "file '#{file}' was filtered out by patterns"
478+
filteredOut.files.push
479+
filename: file
432480

433481
# Our test helper function for delimiting different test cases.
434482
global.test = (description, fn) ->
483+
unless (descPatterns.allows description) and (negDescPatterns.allows description)
484+
onFilteredOut description, fn
485+
return
435486
try
436487
fn.test = {description, currentFile}
437488
result = fn.call(fn)
@@ -445,6 +496,7 @@ runTests = (CoffeeScript) ->
445496
passedTests++
446497
catch err
447498
onFail description, fn, err
499+
console.info "passed: #{description} in #{currentFile}"
448500

449501
helpers.extend global, require './test/support/helpers'
450502

@@ -483,6 +535,9 @@ runTests = (CoffeeScript) ->
483535

484536
startTime = Date.now()
485537
for file in files when helpers.isCoffee file
538+
unless (filePatterns.allows file) and (negFilePatterns.allows file)
539+
onFilteredFile file
540+
continue
486541
literate = helpers.isLiterate file
487542
currentFile = filename = path.join 'test', file
488543
code = fs.readFileSync filename
@@ -495,9 +550,23 @@ runTests = (CoffeeScript) ->
495550
Promise.reject() if failures.length isnt 0
496551

497552

498-
task 'test', 'run the CoffeeScript language test suite', ->
499-
runTests(CoffeeScript).catch -> process.exit 1
500-
553+
option '-f', '--file [REGEXP*]', 'regexp patterns to positively match against test file paths'
554+
option null, '--negFile [REGEXP*]', 'regexp patterns to negatively match against test file paths'
555+
option '-d', '--desc [REGEXP*]', 'regexp patterns to positively match against test descriptions'
556+
option null, '--negDesc [REGEXP*]', 'regexp patterns to negatively match against test descriptions'
557+
558+
task 'test', 'run the CoffeeScript language test suite', ({
559+
file = [],
560+
negFile = [],
561+
desc = [],
562+
negDesc = [],
563+
} = {}) ->
564+
testOptions =
565+
filePatterns: new PatternSet file
566+
negFilePatterns: new PatternSet negFile, {negated: yes}
567+
descPatterns: new PatternSet desc
568+
negDescPatterns: new PatternSet negDesc, {negated: yes}
569+
runTests(CoffeeScript, testOptions).catch -> process.exit 1
501570

502571
task 'test:browser', 'run the test suite against the modern browser compiler in a headless browser', ->
503572
# Create very simple web server to serve the two files we need.

build-support/console.coffee

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
{ Console } = require 'console'
2+
process = require 'process'
3+
4+
exports.CakeConsole = class CakeConsole extends Console
5+
@LEVELS: ['trace', 'debug', 'info', 'log', 'warn', 'error']
6+
@validLevels: => "[#{(@LEVELS.map (l) -> "'#{l}'").join ', '}]"
7+
@checkLevel: (level) =>
8+
unless level in @LEVELS
9+
throw new TypeError "argument '#{level}' was not a valid log level (should be: #{@validLevels()})"
10+
level
11+
12+
constructor: ({level, ...opts} = {}) ->
13+
super opts
14+
@level = @constructor.checkLevel level ? 'log'
15+
16+
@getLevelNum: (l) => @LEVELS.indexOf @checkLevel l
17+
curLevelNum: -> @constructor.getLevelNum @level
18+
doesThisLevelApply: (l) -> @curLevelNum() <= @constructor.getLevelNum(l)
19+
20+
# Always log, regardless of level. This is for terminal output not intended to be configured by
21+
# logging level.
22+
unconditionalLog: (...args) ->
23+
super.log ...args
24+
25+
# Define the named logging methods (.log(), .warn(), ...) by extracting them from the superclass.
26+
trace: (...args) ->
27+
if @doesThisLevelApply 'trace'
28+
super ...args
29+
30+
debug: (...args) ->
31+
if @doesThisLevelApply 'debug'
32+
super ...args
33+
34+
info: (...args) ->
35+
if @doesThisLevelApply 'info'
36+
super ...args
37+
38+
log: (...args) ->
39+
if @doesThisLevelApply 'log'
40+
super ...args
41+
42+
warn: (...args) ->
43+
if @doesThisLevelApply 'warn'
44+
super ...args
45+
46+
error: (...args) ->
47+
if @doesThisLevelApply 'error'
48+
super ...args
49+
50+
# Call .dir(), but filtering by configured level.
51+
dirLevel: (level, ...args) ->
52+
if @doesThisLevelApply level
53+
super.dir ...args
54+
55+
# We want to be able to call .dir() as normal, but we also want to be able to call .dir.log() to
56+
# explicitly set the logging level for .dir().
57+
Object.defineProperty @::, 'dir',
58+
configurable: yes
59+
get: ->
60+
# By default, .dir() uses the 'log' level.
61+
dir = (...args) -> @dirLevel 'log', ...args
62+
Object.defineProperties dir, Object.fromEntries do => for k in @constructor.LEVELS
63+
f = do (k) => (...args) => @dirLevel k, ...args
64+
[k,
65+
enumerable: yes
66+
writable: yes
67+
configurable: yes
68+
value: Object.defineProperty f, 'name',
69+
configurable: yes
70+
value: k]
71+
# We wouldn't normally have to set this, but Console does some wonky prototype munging:
72+
# https://github.com/nodejs/node/blob/17fae65c72321659390c4cbcd9ddaf248accb953/lib/internal/console/constructor.js#L145-L147
73+
set: (dir) -> # ignore
74+
75+
@stdio: ({
76+
stdout = process.stdout,
77+
stderr = process.stderr,
78+
...opts,
79+
} = {}) => new @ {
80+
stdout,
81+
stderr,
82+
...opts
83+
}
84+
85+
86+
exports.setupConsole = ({level, useColors}) ->
87+
if global.cakeConsole?
88+
return global.cakeConsole
89+
90+
opts = {level}
91+
unless useColors
92+
opts.colorMode = no
93+
global.console = global.cakeConsole = cakeConsole = CakeConsole.stdio opts
94+
console.debug "log level = #{level}"
95+
cakeConsole

build-support/patterns.coffee

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
exports.PatternSet = class PatternSet
2+
constructor: (patternStrings = [], {@negated = no} = {}) ->
3+
@matchers = (new RegExp p for p in patternStrings when p isnt '')
4+
5+
isEmpty: -> @matchers.length is 0
6+
7+
iterMatchers: -> @matchers[Symbol.iterator]()
8+
9+
test_: (arg) -> @iterMatchers().some (m) -> m.exec arg
10+
11+
allows: (arg) ->
12+
return yes if @isEmpty()
13+
if @negated
14+
not @test_ arg
15+
else
16+
@test_ arg
17+
18+
@empty: ({negated = no} = {}) => new @ [], {negated}

0 commit comments

Comments
 (0)