Skip to content
This repository has been archived by the owner on Sep 16, 2021. It is now read-only.

Commit

Permalink
Introduce ts_web_test rule which runs karma
Browse files Browse the repository at this point in the history
  • Loading branch information
alexeagle committed Dec 14, 2017
1 parent d734cc5 commit 491949f
Show file tree
Hide file tree
Showing 12 changed files with 324 additions and 1 deletion.
4 changes: 3 additions & 1 deletion defs.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,10 @@ load("//internal:build_defs.bzl", _ts_library = "ts_library")
load("//internal:ts_config.bzl", _ts_config = "ts_config")
load("//internal/devserver:ts_devserver.bzl", _ts_devserver = "ts_devserver_macro")
load("//internal:ts_repositories.bzl", _ts_repositories = "ts_repositories")
load("//internal/karma:ts_web_test.bzl", _ts_web_test = "ts_web_test_macro")

ts_library = _ts_library
ts_config = _ts_config
ts_repositories = _ts_repositories
ts_devserver = _ts_devserver
# TODO(alexeagle): make ts_web_test work in google3
ts_web_test = _ts_web_test
20 changes: 20 additions & 0 deletions examples/testing/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
load("@build_bazel_rules_typescript//:defs.bzl", "ts_library", "ts_web_test")

ts_library(
name = "lib",
srcs = ["decrement.ts"],
)

ts_library(
name = "tests",
srcs = ["decrement.spec.ts"],
deps = [":lib"],
testonly = 1,
)

ts_web_test(
name = "testing",
deps = [
":tests",
],
)
7 changes: 7 additions & 0 deletions examples/testing/decrement.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import {decrement} from './decrement';

describe('decrementing', () => {
it('should do that', () => {
expect(decrement(1)).toBe(0);
});
});
3 changes: 3 additions & 0 deletions examples/testing/decrement.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export function decrement(n: number) {
return n - 1;
}
20 changes: 20 additions & 0 deletions internal/karma/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package(default_visibility=["//visibility:public"])

exports_files(["test-main.js", "karma.conf.js"])

load("@build_bazel_rules_typescript//:defs.bzl", "ts_library")

ts_library(
name = "karma_concat_js",
srcs = glob(["*.ts"]),
module_name = "karma-concat-js",
)

load("@build_bazel_rules_nodejs//:defs.bzl", "nodejs_binary")

nodejs_binary(
name = "karma_bin",
entry_point = "karma/bin/karma",
data = [":karma_concat_js"],
node_modules = "@build_bazel_rules_typescript_karma_deps//:node_modules",
)
74 changes: 74 additions & 0 deletions internal/karma/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/*
* Concat all JS files before serving.
*/
import * as crypto from 'crypto';
import * as fs from 'fs';
import * as path from 'path';
import * as process from 'process';
import * as tmp from 'tmp';
///<reference types="lib.dom"/>

/**
* Return SHA1 of data buffer.
*/
function sha1(data) {
const hash = crypto.createHash('sha1');
hash.update(data);
return hash.digest('hex');
}

/**
* Entry-point for the Karma plugin.
*/
function initConcatJs(logger, emitter, basePath) {
const log = logger.create('framework.concat_js');

// Create a tmp file for the concat bundle that is automatically cleaned up on
// exit.
const tmpFile = tmp.fileSync({keep: false, dir: process.env['TEST_TMPDIR']});

emitter.on('file_list_modified', files => {
const bundleFile = {
path: '/concatjs_bundle.js',
contentPath: tmpFile.name,
isUrl: false,
content: ''
} as any;
const included = [];

files.included.forEach(file => {
if (path.extname(file.originalPath) !== '.js') {
// Preserve all non-JS that were there in the included list.
included.push(file);
} else {
const relativePath = path.relative(basePath, file.originalPath);

// Remove 'use strict'.
let content = file.content.replace(/('use strict'|"use strict");?/,
'');
content = JSON.stringify(
content + '\n//# sourceURL=http://concatjs/base/' +
relativePath + '\n');
content = `//${relativePath}\neval(${content});\n`;
bundleFile.content += content;
}
});

bundleFile.sha = sha1(new Buffer(bundleFile.content));
bundleFile.mtime = new Date();
included.unshift(bundleFile);

files.included = included;
files.served.push(bundleFile);

log.debug('Writing concatjs bundle to tmp file %s',
bundleFile.contentPath);
fs.writeFileSync(bundleFile.contentPath, bundleFile.content);
});
}

(initConcatJs as any).$inject = ['logger', 'emitter', 'config.basePath'];

module.exports = {
'framework:concat_js': ['factory', initConcatJs]
};
56 changes: 56 additions & 0 deletions internal/karma/karma.conf.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// Karma configuration
// GENERATED BY Bazel
module.exports = function(config) {
if (process.env['IBAZEL_NOTIFY_CHANGES'] === 'y') {
// Tell karma to only listen for ibazel messages on stdin rather than watch all the input files
// This is from fork alexeagle/karma in the ibazel branch:
// https://github.com/alexeagle/karma/blob/576d262af50b10e63485b86aee99c5358958c4dd/lib/server.js#L172
config.set({watchMode: 'ibazel'});
}

config.set({
// base path that will be used to resolve all patterns (eg. files, exclude)
basePath: 'TMPL_runfiles_path',
files: [
'build_bazel_rules_typescript_karma_deps/node_modules/karma/requirejs.config.tpl.js',
TMPL_files
],
plugins: ['karma-*', 'karma-concat-js'],
frameworks: ['jasmine', 'concat_js', 'requirejs'],

// test results reporter to use
// possible values: 'dots', 'progress'
// available reporters: https://npmjs.org/browse/keyword/karma-reporter
reporters: ['progress'],


// web server port
port: 9876,


// enable / disable colors in the output (reporters and logs)
colors: true,


// level of logging
// possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
logLevel: config.LOG_INFO,


// enable / disable watching file and executing tests whenever any file changes
autoWatch: true,

// start these browsers
// available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
browsers: ['DISPLAY' in Object.keys(process.env) ? 'Chrome': 'ChromeHeadless'],

// Continuous Integration mode
// if true, Karma captures browsers, runs the tests and exits
// note: run_karma.sh may override this as a command-line option.
singleRun: false,

// Concurrency level
// how many browser should be started simultaneous
concurrency: Infinity
})
}
13 changes: 13 additions & 0 deletions internal/karma/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"description": "runtime dependences for ts_web_test rules",
"devDependencies": {
"@types/tmp": "0.0.33",
"jasmine-core": "2.8.0",
"karma": "alexeagle/karma#fa1a84ac881485b5657cb669e9b4e5da77b79f0a",
"karma-chrome-launcher": "2.2.0",
"karma-jasmine": "1.1.1",
"karma-requirejs": "1.1.0",
"requirejs": "2.3.5",
"tmp": "0.0.33"
}
}
118 changes: 118 additions & 0 deletions internal/karma/ts_web_test.bzl
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
# Copyright 2017 The Bazel Authors. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Implementation of the ts_web_test rule."""

load("@build_bazel_rules_nodejs//internal:node.bzl",
"sources_aspect",
"expand_path_into_runfiles",
)

_CONF_TMPL = "//internal/karma:karma.conf.js"
_LOADER = "@build_bazel_rules_typescript_karma_deps//:node_modules/karma/requirejs.config.tpl.js"

def _ts_web_test_impl(ctx):
conf = ctx.actions.declare_file(
"%s.conf.js" % ctx.label.name,
sibling=ctx.outputs.executable)

files = depset(ctx.files.srcs)
for d in ctx.attr.deps:
if hasattr(d, "node_sources"):
files += d.node_sources
elif hasattr(d, "files"):
files += d.files

files_entries = [
" '%s'," % expand_path_into_runfiles(ctx, f.short_path)
for f in files
]

# root-relative (runfiles) path to the directory containing karma.conf
config_segments = len(conf.short_path.split("/"))

ctx.actions.expand_template(
output = conf,
template = ctx.file._conf_tmpl,
substitutions = {
"TMPL_runfiles_path": "/".join([".."] * config_segments),
"TMPL_files": "\n".join(files_entries),
"TMPL_workspace_name": ctx.workspace_name,
})

ctx.actions.write(
output = ctx.outputs.executable,
is_executable = True,
content = """#!/usr/bin/env bash
readonly KARMA={TMPL_karma}
readonly CONF={TMPL_conf}
export HOME=$(mktemp -d)
ARGV=( "start" $CONF )
# Detect that we are running as a test, by using a well-known environment
# variable. See go/test-encyclopedia
if [ ! -z "$TEST_TMPDIR" ]; then
ARGV+=( "--single-run" )
fi
$KARMA ${{ARGV[@]}}
""".format(TMPL_karma = ctx.executable._karma.short_path,
TMPL_conf = conf.short_path))
return [DefaultInfo(
runfiles = ctx.runfiles(
files = ctx.files.srcs + ctx.files.deps + [
conf,
ctx.file._loader,
],
transitive_files = files,
# Propagate karma_bin and its runfiles
collect_data = True,
collect_default = True,
),
)]

ts_web_test = rule(
implementation = _ts_web_test_impl,
test = True,
attrs = {
"srcs": attr.label_list(allow_files = ["js"]),
"deps": attr.label_list(
allow_files = True,
aspects = [sources_aspect],
),
"data": attr.label_list(cfg = "data"),
"_karma": attr.label(
default = Label("//internal/karma:karma_bin"),
executable = True,
cfg = "data",
single_file = False,
allow_files = True),
"_conf_tmpl": attr.label(
default = Label(_CONF_TMPL),
allow_files = True, single_file = True),
"_loader": attr.label(
default = Label(_LOADER),
allow_files = True, single_file = True),
},
)

# This macro exists only to modify the users rule definition a bit.
# DO NOT add composition of additional rules here.
def ts_web_test_macro(tags = [], data = [], **kwargs):
ts_web_test(
# Users don't need to know that this tag is required to run under ibazel
tags = tags + ["ibazel_notify_changes"],
# Our binary dependency must be in data[] for collect_data to pick it up
# FIXME: maybe we can just ask the attr._karma for its runfiles attr
data = data + ["@build_bazel_rules_typescript//internal/karma:karma_bin"],
**kwargs)
5 changes: 5 additions & 0 deletions internal/ts_repositories.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -59,3 +59,8 @@ def ts_repositories(default_tsconfig = None):
name = "build_bazel_rules_typescript_devserver_deps",
package_json = "@build_bazel_rules_typescript//internal/devserver:package.json",
)

npm_install(
name = "build_bazel_rules_typescript_karma_deps",
package_json = "@build_bazel_rules_typescript//internal/karma:package.json",
)
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"typescript": ">=2.4.2"
},
"devDependencies": {
"@bazel/ibazel": "^0.2.0",
"@types/jasmine": "^2.8.2",
"@types/node": "7.0.18",
"@types/source-map": "^0.5.1",
Expand Down
4 changes: 4 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@
# yarn lockfile v1


"@bazel/ibazel@^0.2.0":
version "0.2.0"
resolved "https://registry.yarnpkg.com/@bazel/ibazel/-/ibazel-0.2.0.tgz#c119aef4344a789cef5e792caaee52264123e71c"

"@types/jasmine@^2.8.2":
version "2.8.2"
resolved "https://registry.yarnpkg.com/@types/jasmine/-/jasmine-2.8.2.tgz#6ae4d8740c0da5d5a627df725b4eed71b8e36668"
Expand Down

0 comments on commit 491949f

Please sign in to comment.