Skip to content

Commit

Permalink
Drop Node < 6, add --respawn-as/--env
Browse files Browse the repository at this point in the history
Additional edits:

- `--{no-,}respawn` and `--{no,}force-local` removed, with flags ignored
- CLI init lookup bug fixed. (fault tolerance FTW?)
- CLI init slightly streamlined.
  • Loading branch information
dead-claudia committed Jun 6, 2017
1 parent db3cf15 commit e23c073
Show file tree
Hide file tree
Showing 29 changed files with 551 additions and 368 deletions.
5 changes: 2 additions & 3 deletions .travis.yml
@@ -1,10 +1,9 @@
language: node_js

node_js:
- "4"
- "5"
- "6"
- "7"
- "8"

os:
- osx
Expand Down Expand Up @@ -37,7 +36,7 @@ script:
matrix:
fast_finish: true
include:
# Browser tests
# Browser tests, uses current LTS
- node_js: "6"
env: ENV=chrome
os: linux
Expand Down
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Expand Up @@ -65,7 +65,7 @@ I personally frequently use `node make watch:node` when working on this, so I ha

This is tested in [Travis CI](https://travis-ci.org/isiahmeadows/thallium) on Ubuntu against the following runtimes:

- Node 4 and later on Windows, Linux (Ubuntu), and OS X
- Node on [current LTS](https://github.com/nodejs/LTS/) and later on Windows, Linux (Ubuntu), and OS X
- PhantomJS 2 on Windows, Linux (Ubuntu), and OS X
- Chrome Stable on Linux (Ubuntu)
- Firefox Stable, ESR, and Beta on Linux (Ubuntu)
Expand Down
2 changes: 1 addition & 1 deletion README.md
Expand Up @@ -3,7 +3,7 @@
[![Travis Build Status](https://travis-ci.org/isiahmeadows/thallium.svg?branch=master)](https://travis-ci.org/isiahmeadows/thallium) [![AppVeyor Build status](https://ci.appveyor.com/api/projects/status/f9lhn8ivfwj39k7k?svg=true)](https://ci.appveyor.com/project/isiahmeadows/thallium)
[![Join the chat at https://gitter.im/isiahmeadows/thallium](https://badges.gitter.im/isiahmeadows/thallium.svg)](https://gitter.im/isiahmeadows/thallium?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)

A simple, unopinionated, modular test framework meant to simplify your tests. It supports Node 4 and later, as well as PhantomJS 2 and browsers (tested in Chrome and Firefox).
A simple, unopinionated, modular test framework meant to simplify your tests. It supports Node (from the [current LTS](https://github.com/nodejs/LTS/)), as well as PhantomJS 2 and browsers (tested in Chrome and Firefox).

*Note that this is a huge work in progress, and is probably not suited for production projects.*

Expand Down
6 changes: 2 additions & 4 deletions appveyor.yml
@@ -1,14 +1,12 @@
version: '{build}'
environment:
matrix:
- nodejs_version: 4
task: test:node
- nodejs_version: 5
task: test:node
- nodejs_version: 6
task: test:node
- nodejs_version: 7
task: test:node
- nodejs_version: 8
task: test:node
- nodejs_version: 6
task: test:phantomjs

Expand Down
3 changes: 3 additions & 0 deletions bin/.eslintrc.yml
@@ -0,0 +1,3 @@
extends: ../lib/cli/.eslintrc.yml
env:
node: true
47 changes: 18 additions & 29 deletions bin/tl.js
@@ -1,12 +1,12 @@
#!/usr/bin/env node
/* eslint-env node */
"use strict"

/**
* This script loads Thallium, and respawns Node if necessary with the proper
* CLI flags (if other arguments are passed).
* This is a thin layer of indirection to find and execute the correct init, so
* it's not otherwise coupled to the CLI initialization process.
*
* It also tries to delegate to the local installation as much as possible.
* Note: this hijacks Node's module resolution algorithm to require files as if
* from a fake module in the correct base directory.
*/

if (require.main !== module) {
Expand All @@ -15,31 +15,20 @@ if (require.main !== module) {

var path = require("path")
var Module = require("module")
var parse = load(process.cwd(), "parse.js", false)
var args = parse(process.argv.slice(2))
var base = args.cwd != null ? path.resolve(args.cwd) : process.cwd()
var init

// Respawn with the local version
load(base, "init.js", args.forceLocal)(args)
try {
var baseDir = path.resolve(process.cwd())
var m = new Module(path.join(baseDir, "dummy.js"), undefined)

// Prefer a local installation to a global one if at all possible
function load(baseDir, name, forceLocal) {
// Hack: hijack Node's internal resolution algorithm to require the file
// as if from a fake module in the correct base directory. It also will
// avoid several bugs with the `resolve` module (Node's is necessarily more
// stable).
try {
if (!forceLocal) {
var m = new Module(path.resolve(baseDir, "dummy.js"), undefined)

m.filename = m.id
m.paths = Module._nodeModulePaths(baseDir)
m.loaded = true
return m.require("thallium/" + name)
}
} catch (_) {
// do nothing
}

return require("../lib/cli/" + name) // eslint-disable-line global-require
m.filename = m.id
m.paths = Module._nodeModulePaths(baseDir)
m.loaded = true
init = m.require("thallium/lib/cli/init.js")
} catch (_) {
init = require("../lib/cli/init.js") // eslint-disable-line global-require
}

// Note: This *must* be called after module load, so that errors thrown don't
// result in running the same code twice.
init()
9 changes: 9 additions & 0 deletions fixtures/binaries/print-args.cmd
@@ -0,0 +1,9 @@
@echo off
cd
:loop
IF NOT "%~1"=="" (
ECHO %1
SHIFT
GOTO :loop
)
%PROGRAM% %BINARY%
6 changes: 6 additions & 0 deletions fixtures/binaries/print-args.sh
@@ -0,0 +1,6 @@
#!/usr/bin/env sh
pwd
for i in "$@"; do
echo "$i"
done
${PROGRAM} ${BINARY}
3 changes: 3 additions & 0 deletions fixtures/env/node_modules/thallium.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

13 changes: 13 additions & 0 deletions fixtures/env/test.js
@@ -0,0 +1,13 @@
"use strict"

/* eslint-env node */
var t = require("thallium")

// Don't print anything
t.reporter(function () {
return function () {}
})

console.log("ENV_FOO = " + process.env.ENV_FOO)
console.log("ENV_BAR = " + process.env.ENV_BAR)
console.log("ENV_BAZ = " + process.env.ENV_BAZ)
5 changes: 3 additions & 2 deletions fixtures/mid-coffee/spec/common.coffee
Expand Up @@ -7,8 +7,7 @@ represent more real-world usage.

t = require 'thallium'
path = require 'path'
Common = require '../../../lib/cli/common.js'
{globParent: gp} = require '../../../lib/cli/init-common.js'
Common = require '../../../lib/cli/common'
assert = require 'thallium/assert'

t.test 'cli common', ->
Expand Down Expand Up @@ -190,6 +189,8 @@ t.test 'cli common', ->
)

t.test 'globParent()', ->
gp = Common.globParent

t.test 'strips glob magic to return parent path', ->
assert.equal gp(p('path/to/*.js')), p('path/to')
assert.equal gp(p('/root/path/to/*.js')), p('/root/path/to')
Expand Down
202 changes: 202 additions & 0 deletions lib/cli/args.js
@@ -0,0 +1,202 @@
"use strict"

var hasOwn = Object.prototype.hasOwnProperty

// TODO: Duplicate this in the `tl` binary to just preparse for known/unknown
// options when starting up initially, so it's more future-proof

// Errors that aren't from this borking up. It intentionally doesn't have a
// stack trace.
function warn(message) {
return {message: "Warning: " + message}
}

/**
* Properties:
*
* color: If not `null`, force the color on if `true`, off if `false`.
*
* config: The config file to use. The default is inferred from
* `${args.files[0]}/.tl.${ext}`, taking the first `ext` from
* `--require` or whatever's inferred from node-interpret.
*
* cwd: This changes the default current working directory. It normally defaults
* to `process.cwd()` or whatever was passed for `--cwd`, but the unit
* tests may change that default.
*
* env: This is a key-value mapping for the environment Node is to be respawned
* with.
*
* files: A list of file globs to load.
*
* help: If set to `"simple"` or `"detailed"`, display the relevant help prompt.
*
* opts: The `.tl.opts` file as used in the init script.
*
* require: A list of extensions + possible modules to require/register. This
* effectively disables much of the inferrence magic based on `cwd`,
* the first `files` glob, and `config` to come up with something
* sensible.
*
* respawnAs: If set, it's the binary to respawn with. Setting this also implies
* setting `respawn` to `true` ignorant of other options.
*
* unknown: This contains all unknown flags, so they can be passed to Node
* transparently (unless `respawn === false`).
*/
exports.Args = Args
function Args() {
this.color = undefined
this.config = undefined
this.cwd = undefined
this.env = undefined
this.files = []
this.help = undefined
this.opts = undefined
this.require = []
this.respawnAs = undefined
this.unknown = []
}

// `true` means it requires a value. If the key doesn't exist, the flag is
// implicitly false, since this table is checked for truthiness, not actual
// existence + true/false.
var requiresValue = {
"config": true,
"cwd": true,
"env": true,
"opts": true,
"require": true,
"respawn-as": true,
}

var aliases = {
c: "config",
e: "env",
H: "help-detailed",
h: "help",
r: "require",
}

/**
* Serializes `args` into a list of tokens.
*/
function serialize(args, call) {
var boolean = true
var i = 0

while (i < args.length && args[i] !== "--") {
var entry = args[i++]

if (!boolean || entry[0] !== "-") {
// Allow anything other than literally `--` as a value. If it's a
// mistake, this'll likely complain later, anyways.
call("value", entry, boolean)
boolean = true
} else if (entry[1] === "-") {
var value = entry.slice(2)

boolean = !requiresValue[value]
call("flag", value, boolean)
} else {
var last

for (var j = 1; j < entry.length; j++) {
var short = entry[j]

// If we're not yet done parsing the shorthand alias, then the
// current binary option *clearly* won't have a value to use.
if (!boolean) {
throw warn(
"Shorthand option -" + last + " requires a value " +
"immediately after it")
}

// Silently ignore invalid short flags - V8 doesn't use any.
if (hasOwn.call(aliases, short)) {
var alias = aliases[short]

boolean = !requiresValue[alias]
call("flag", alias, boolean)
}

last = short
}
}
}

if (!boolean) {
throw warn(
"Shorthand option -" + args[i - 1] + " requires a value " +
"immediately after it")
}

// The above loop only breaks early with an `--` argument, and this loop's
// preincrement in its condition handles this as well.
while (++i < args.length) {
call("file", args[i], true)
}
}

exports.parse = function (args) {
var result = new Args()
var lastBoolean = false
var lastValue

serialize(args, function (type, value, boolean) {
if (type === "flag") {
switch (value) {
case "help": result.help = "simple"; break
case "help-detailed": result.help = "detailed"; break
case "color": result.color = true; break
case "no-color": result.color = false; break

case "config": case "cwd": case "env":
case "opts": case "require": case "respawn-as":
lastValue = value
lastBoolean = boolean
return

// Legacy options - ignore them, and remove in 0.5.
case "force-local": case "no-force-local":
case "respawn": case "no-respawn":
break

default:
result.unknown.push("--" + value)
}
} else if (lastValue == null || type === "file") {
result.files.push(value)
} else if (lastValue === "env") {
var index = value.indexOf("=")

if (result.env == null) result.env = Object.create(null)
result.env[value.slice(0, index)] = value.slice(index + 1)
} else {
if (lastValue === "respawn-as") {
lastValue = "respawnAs"
}

// Silently ignore invalid arguments
var current = result[lastValue]

if (Array.isArray(current)) {
current.push(value)
} else if (lastBoolean) {
result[lastValue] = true
result.files.push(value)
} else {
result[lastValue] = value
}
}

lastValue = null
})

if (lastValue != null && !lastBoolean) {
throw warn(
"Option was passed without a required argument: " + lastValue)
}

return result
}

0 comments on commit e23c073

Please sign in to comment.