1 | var path = require('path')
|
2 |
|
3 | var logger = require('./logger')
|
4 | var log = logger.create('config')
|
5 | var helper = require('./helper')
|
6 | var constant = require('./constants')
|
7 |
|
8 | var COFFEE_SCRIPT_AVAILABLE = false
|
9 | var LIVE_SCRIPT_AVAILABLE = false
|
10 |
|
11 |
|
12 |
|
13 | try {
|
14 | require('coffee-script').register()
|
15 | COFFEE_SCRIPT_AVAILABLE = true
|
16 | } catch (e) {}
|
17 |
|
18 |
|
19 |
|
20 | try {
|
21 | require('LiveScript')
|
22 | LIVE_SCRIPT_AVAILABLE = true
|
23 | } catch (e) {}
|
24 |
|
25 | var Pattern = function (pattern, served, included, watched, nocache) {
|
26 | this.pattern = pattern
|
27 | this.served = helper.isDefined(served) ? served : true
|
28 | this.included = helper.isDefined(included) ? included : true
|
29 | this.watched = helper.isDefined(watched) ? watched : true
|
30 | this.nocache = helper.isDefined(nocache) ? nocache : false
|
31 | }
|
32 |
|
33 | var UrlPattern = function (url) {
|
34 | Pattern.call(this, url, false, true, false, false)
|
35 | }
|
36 |
|
37 | var createPatternObject = function (pattern) {
|
38 | if (pattern && helper.isString(pattern)) {
|
39 | return helper.isUrlAbsolute(pattern) ? new UrlPattern(pattern) : new Pattern(pattern)
|
40 | }
|
41 |
|
42 | if (helper.isObject(pattern)) {
|
43 | if (pattern.pattern && helper.isString(pattern.pattern)) {
|
44 | return helper.isUrlAbsolute(pattern.pattern)
|
45 | ? new UrlPattern(pattern.pattern)
|
46 | : new Pattern(
|
47 | pattern.pattern,
|
48 | pattern.served,
|
49 | pattern.included,
|
50 | pattern.watched,
|
51 | pattern.nocache)
|
52 | }
|
53 |
|
54 | log.warn('Invalid pattern %s!\n\tObject is missing "pattern" property.', pattern)
|
55 | return new Pattern(null, false, false, false, false)
|
56 | }
|
57 |
|
58 | log.warn('Invalid pattern %s!\n\tExpected string or object with "pattern" property.', pattern)
|
59 | return new Pattern(null, false, false, false, false)
|
60 | }
|
61 |
|
62 | var normalizeUrlRoot = function (urlRoot) {
|
63 | var normalizedUrlRoot = urlRoot
|
64 |
|
65 | if (normalizedUrlRoot.charAt(0) !== '/') {
|
66 | normalizedUrlRoot = '/' + normalizedUrlRoot
|
67 | }
|
68 |
|
69 | if (normalizedUrlRoot.charAt(normalizedUrlRoot.length - 1) !== '/') {
|
70 | normalizedUrlRoot = normalizedUrlRoot + '/'
|
71 | }
|
72 |
|
73 | if (normalizedUrlRoot !== urlRoot) {
|
74 | log.warn('urlRoot normalized to "%s"', normalizedUrlRoot)
|
75 | }
|
76 |
|
77 | return normalizedUrlRoot
|
78 | }
|
79 |
|
80 | var normalizeConfig = function (config, configFilePath) {
|
81 | var basePathResolve = function (relativePath) {
|
82 | if (helper.isUrlAbsolute(relativePath)) {
|
83 | return relativePath
|
84 | }
|
85 |
|
86 | if (!helper.isDefined(config.basePath) || !helper.isDefined(relativePath)) {
|
87 | return ''
|
88 | }
|
89 | return path.resolve(config.basePath, relativePath)
|
90 | }
|
91 |
|
92 | var createPatternMapper = function (resolve) {
|
93 | return function (objectPattern) {
|
94 | objectPattern.pattern = resolve(objectPattern.pattern)
|
95 |
|
96 | return objectPattern
|
97 | }
|
98 | }
|
99 |
|
100 | if (helper.isString(configFilePath)) {
|
101 |
|
102 | config.basePath = path.resolve(path.dirname(configFilePath), config.basePath)
|
103 |
|
104 |
|
105 | config.exclude.push(configFilePath)
|
106 | } else {
|
107 | config.basePath = path.resolve(config.basePath || '.')
|
108 | }
|
109 |
|
110 | config.files = config.files.map(createPatternObject).map(createPatternMapper(basePathResolve))
|
111 | config.exclude = config.exclude.map(basePathResolve)
|
112 |
|
113 |
|
114 | config.basePath = helper.normalizeWinPath(config.basePath)
|
115 | config.files = config.files.map(createPatternMapper(helper.normalizeWinPath))
|
116 | config.exclude = config.exclude.map(helper.normalizeWinPath)
|
117 |
|
118 |
|
119 | config.urlRoot = normalizeUrlRoot(config.urlRoot)
|
120 |
|
121 |
|
122 | config.protocol = (config.protocol || 'http').split(':')[0] + ':'
|
123 | if (config.protocol.match(/https?:/) === null) {
|
124 | log.warn('"%s" is not a supported protocol, defaulting to "http:"',
|
125 | config.protocol)
|
126 | config.protocol = 'http:'
|
127 | }
|
128 |
|
129 | if (config.proxies && config.proxies.hasOwnProperty(config.urlRoot)) {
|
130 | log.warn('"%s" is proxied, you should probably change urlRoot to avoid conflicts',
|
131 | config.urlRoot)
|
132 | }
|
133 |
|
134 | if (config.singleRun && config.autoWatch) {
|
135 | log.debug('autoWatch set to false, because of singleRun')
|
136 | config.autoWatch = false
|
137 | }
|
138 |
|
139 | if (!config.singleRun && config.browserDisconnectTolerance) {
|
140 | log.debug('browserDisconnectTolerance set to 0, because of singleRun')
|
141 | config.browserDisconnectTolerance = 0
|
142 | }
|
143 |
|
144 | if (helper.isString(config.reporters)) {
|
145 | config.reporters = config.reporters.split(',')
|
146 | }
|
147 |
|
148 | if (config.client && config.client.args && !Array.isArray(config.client.args)) {
|
149 | throw new Error('Invalid configuration: client.args must be an array of strings')
|
150 | }
|
151 |
|
152 | if (config.browsers && Array.isArray(config.browsers) === false) {
|
153 | throw new TypeError('Invalid configuration: browsers option must be an array')
|
154 | }
|
155 |
|
156 | var defaultClient = config.defaultClient || {}
|
157 | Object.keys(defaultClient).forEach(function (key) {
|
158 | var option = config.client[key]
|
159 | config.client[key] = helper.isDefined(option) ? option : defaultClient[key]
|
160 | })
|
161 |
|
162 |
|
163 | var preprocessors = config.preprocessors || {}
|
164 | var normalizedPreprocessors = config.preprocessors = Object.create(null)
|
165 |
|
166 | Object.keys(preprocessors).forEach(function (pattern) {
|
167 | var normalizedPattern = helper.normalizeWinPath(basePathResolve(pattern))
|
168 |
|
169 | normalizedPreprocessors[normalizedPattern] = helper.isString(preprocessors[pattern])
|
170 | ? [preprocessors[pattern]] : preprocessors[pattern]
|
171 | })
|
172 |
|
173 |
|
174 | var module = Object.create(null)
|
175 | var hasSomeInlinedPlugin = false
|
176 | var types = ['launcher', 'preprocessor', 'reporter']
|
177 |
|
178 | types.forEach(function (type) {
|
179 | var definitions = config['custom' + helper.ucFirst(type) + 's'] || {}
|
180 |
|
181 | Object.keys(definitions).forEach(function (name) {
|
182 | var definition = definitions[name]
|
183 |
|
184 | if (!helper.isObject(definition)) {
|
185 | return log.warn('Can not define %s %s. Definition has to be an object.', type, name)
|
186 | }
|
187 |
|
188 | if (!helper.isString(definition.base)) {
|
189 | return log.warn('Can not define %s %s. Missing base %s.', type, name, type)
|
190 | }
|
191 |
|
192 | var token = type + ':' + definition.base
|
193 | var locals = {
|
194 | args: ['value', definition]
|
195 | }
|
196 |
|
197 | module[type + ':' + name] = ['factory', function (injector) {
|
198 | var plugin = injector.createChild([locals], [token]).get(token)
|
199 | if (type === 'launcher' && helper.isDefined(definition.displayName)) {
|
200 | plugin.displayName = definition.displayName
|
201 | }
|
202 | return plugin
|
203 | }]
|
204 | hasSomeInlinedPlugin = true
|
205 | })
|
206 | })
|
207 |
|
208 | if (hasSomeInlinedPlugin) {
|
209 | config.plugins.push(module)
|
210 | }
|
211 |
|
212 | return config
|
213 | }
|
214 |
|
215 | var Config = function () {
|
216 | var config = this
|
217 |
|
218 | this.LOG_DISABLE = constant.LOG_DISABLE
|
219 | this.LOG_ERROR = constant.LOG_ERROR
|
220 | this.LOG_WARN = constant.LOG_WARN
|
221 | this.LOG_INFO = constant.LOG_INFO
|
222 | this.LOG_DEBUG = constant.LOG_DEBUG
|
223 |
|
224 | this.set = function (newConfig) {
|
225 | Object.keys(newConfig).forEach(function (key) {
|
226 | config[key] = newConfig[key]
|
227 | })
|
228 | }
|
229 |
|
230 |
|
231 | this.frameworks = []
|
232 | this.protocol = 'http:'
|
233 | this.port = constant.DEFAULT_PORT
|
234 | this.hostname = constant.DEFAULT_HOSTNAME
|
235 | this.httpsServerConfig = {}
|
236 | this.basePath = ''
|
237 | this.files = []
|
238 | this.exclude = []
|
239 | this.logLevel = constant.LOG_INFO
|
240 | this.colors = true
|
241 | this.autoWatch = true
|
242 | this.autoWatchBatchDelay = 250
|
243 | this.restartOnFileChange = false
|
244 | this.usePolling = process.platform === 'darwin' || process.platform === 'linux'
|
245 | this.reporters = ['progress']
|
246 | this.singleRun = false
|
247 | this.browsers = []
|
248 | this.captureTimeout = 60000
|
249 | this.proxies = {}
|
250 | this.proxyValidateSSL = true
|
251 | this.preprocessors = {}
|
252 | this.urlRoot = '/'
|
253 | this.reportSlowerThan = 0
|
254 | this.loggers = [constant.CONSOLE_APPENDER]
|
255 | this.transports = ['polling', 'websocket']
|
256 | this.forceJSONP = false
|
257 | this.plugins = ['karma-*']
|
258 | this.defaultClient = this.client = {
|
259 | args: [],
|
260 | useIframe: true,
|
261 | captureConsole: true,
|
262 | clearContext: true
|
263 | }
|
264 | this.browserDisconnectTimeout = 2000
|
265 | this.browserDisconnectTolerance = 0
|
266 | this.browserNoActivityTimeout = 10000
|
267 | this.concurrency = Infinity
|
268 | this.failOnEmptyTestSuite = true
|
269 | this.retryLimit = 2
|
270 | }
|
271 |
|
272 | var CONFIG_SYNTAX_HELP = ' module.exports = function(config) {\n' +
|
273 | ' config.set({\n' +
|
274 | ' // your config\n' +
|
275 | ' });\n' +
|
276 | ' };\n'
|
277 |
|
278 | var parseConfig = function (configFilePath, cliOptions) {
|
279 | var configModule
|
280 | if (configFilePath) {
|
281 | log.debug('Loading config %s', configFilePath)
|
282 |
|
283 | try {
|
284 | configModule = require(configFilePath)
|
285 | } catch (e) {
|
286 | if (e.code === 'MODULE_NOT_FOUND' && e.message.indexOf(configFilePath) !== -1) {
|
287 | log.error('File %s does not exist!', configFilePath)
|
288 | } else {
|
289 | log.error('Invalid config file!\n ' + e.stack)
|
290 |
|
291 | var extension = path.extname(configFilePath)
|
292 | if (extension === '.coffee' && !COFFEE_SCRIPT_AVAILABLE) {
|
293 | log.error('You need to install CoffeeScript.\n' +
|
294 | ' npm install coffee-script --save-dev')
|
295 | } else if (extension === '.ls' && !LIVE_SCRIPT_AVAILABLE) {
|
296 | log.error('You need to install LiveScript.\n' +
|
297 | ' npm install LiveScript --save-dev')
|
298 | }
|
299 | }
|
300 | return process.exit(1)
|
301 | }
|
302 | if (!helper.isFunction(configModule)) {
|
303 | log.error('Config file must export a function!\n' + CONFIG_SYNTAX_HELP)
|
304 | return process.exit(1)
|
305 | }
|
306 | } else {
|
307 | log.debug('No config file specified.')
|
308 |
|
309 | configModule = function () {}
|
310 | }
|
311 |
|
312 | var config = new Config()
|
313 | config.set(cliOptions)
|
314 |
|
315 | try {
|
316 | configModule(config)
|
317 | } catch (e) {
|
318 | log.error('Error in config file!\n', e)
|
319 | return process.exit(1)
|
320 | }
|
321 |
|
322 |
|
323 | config.set(cliOptions)
|
324 |
|
325 |
|
326 | logger.setup(config.logLevel, config.colors, config.loggers)
|
327 |
|
328 | return normalizeConfig(config, configFilePath)
|
329 | }
|
330 |
|
331 |
|
332 | exports.parseConfig = parseConfig
|
333 | exports.Pattern = Pattern
|
334 | exports.createPatternObject = createPatternObject
|
335 | exports.Config = Config
|