##// END OF EJS Templates
Add --url option to iptest
Jonathan Frederic -
Show More
@@ -1,704 +1,704 b''
1 1 //
2 2 // Utility functions for the HTML notebook's CasperJS tests.
3 3 //
4 4 casper.get_notebook_server = function () {
5 5 // Get the URL of a notebook server on which to run tests.
6 port = casper.cli.get("port");
6 var port = casper.cli.get("port");
7 7 port = (typeof port === 'undefined') ? '8888' : port;
8 return 'http://127.0.0.1:' + port;
8 return casper.cli.get("url") || 'http://127.0.0.1:' + port;
9 9 };
10 10
11 11 casper.open_new_notebook = function () {
12 12 // Create and open a new notebook.
13 13 var baseUrl = this.get_notebook_server();
14 14 this.start(baseUrl);
15 15 this.waitFor(this.page_loaded);
16 16 this.thenClick('button#new_notebook');
17 17 this.waitForPopup('');
18 18
19 19 this.withPopup('', function () {this.waitForSelector('.CodeMirror-code');});
20 20 this.then(function () {
21 21 this.open(this.popups[0].url);
22 22 });
23 23 this.waitFor(this.page_loaded);
24 24
25 25 // Make sure the kernel has started
26 26 this.waitFor(this.kernel_running);
27 27 // track the IPython busy/idle state
28 28 this.thenEvaluate(function () {
29 29 require(['base/js/namespace', 'base/js/events'], function (IPython, events) {
30 30
31 31 events.on('kernel_idle.Kernel',function () {
32 32 IPython._status = 'idle';
33 33 });
34 34 events.on('kernel_busy.Kernel',function () {
35 35 IPython._status = 'busy';
36 36 });
37 37 });
38 38 });
39 39
40 40 // Because of the asynchronous nature of SlimerJS (Gecko), we need to make
41 41 // sure the notebook has actually been loaded into the IPython namespace
42 42 // before running any tests.
43 43 this.waitFor(function() {
44 44 return this.evaluate(function () {
45 45 return IPython.notebook;
46 46 });
47 47 });
48 48 };
49 49
50 50 casper.page_loaded = function() {
51 51 // Return whether or not the kernel is running.
52 52 return this.evaluate(function() {
53 53 return typeof IPython !== "undefined" &&
54 54 IPython.page !== undefined;
55 55 });
56 56 };
57 57
58 58 casper.kernel_running = function() {
59 59 // Return whether or not the kernel is running.
60 60 return this.evaluate(function() {
61 61 return IPython.notebook.kernel.is_connected();
62 62 });
63 63 };
64 64
65 65 casper.kernel_disconnected = function() {
66 66 return this.evaluate(function() {
67 67 return IPython.notebook.kernel.is_fully_disconnected();
68 68 });
69 69 };
70 70
71 71 casper.wait_for_kernel_ready = function () {
72 72 this.waitFor(this.kernel_running);
73 73 this.thenEvaluate(function () {
74 74 IPython._kernel_ready = false;
75 75 IPython.notebook.kernel.kernel_info(
76 76 function () {
77 77 IPython._kernel_ready = true;
78 78 });
79 79 });
80 80 this.waitFor(function () {
81 81 return this.evaluate(function () {
82 82 return IPython._kernel_ready;
83 83 });
84 84 });
85 85 };
86 86
87 87 casper.shutdown_current_kernel = function () {
88 88 // Shut down the current notebook's kernel.
89 89 this.thenEvaluate(function() {
90 90 IPython.notebook.session.delete();
91 91 });
92 92 // We close the page right after this so we need to give it time to complete.
93 93 this.wait(1000);
94 94 };
95 95
96 96 casper.delete_current_notebook = function () {
97 97 // Delete created notebook.
98 98
99 99 // For some unknown reason, this doesn't work?!?
100 100 this.thenEvaluate(function() {
101 101 IPython.notebook.delete();
102 102 });
103 103 };
104 104
105 105 casper.wait_for_busy = function () {
106 106 // Waits for the notebook to enter a busy state.
107 107 this.waitFor(function () {
108 108 return this.evaluate(function () {
109 109 return IPython._status == 'busy';
110 110 });
111 111 });
112 112 };
113 113
114 114 casper.wait_for_idle = function () {
115 115 // Waits for the notebook to idle.
116 116 this.waitFor(function () {
117 117 return this.evaluate(function () {
118 118 return IPython._status == 'idle';
119 119 });
120 120 });
121 121 };
122 122
123 123 casper.wait_for_output = function (cell_num, out_num) {
124 124 // wait for the nth output in a given cell
125 125 this.wait_for_idle();
126 126 out_num = out_num || 0;
127 127 this.then(function() {
128 128 this.waitFor(function (c, o) {
129 129 return this.evaluate(function get_output(c, o) {
130 130 var cell = IPython.notebook.get_cell(c);
131 131 return cell.output_area.outputs.length > o;
132 132 },
133 133 // pass parameter from the test suite js to the browser code js
134 134 {c : cell_num, o : out_num});
135 135 });
136 136 },
137 137 function then() { },
138 138 function timeout() {
139 139 this.echo("wait_for_output timed out!");
140 140 });
141 141 };
142 142
143 143 casper.wait_for_widget = function (widget_info) {
144 144 // wait for a widget msg que to reach 0
145 145 //
146 146 // Parameters
147 147 // ----------
148 148 // widget_info : object
149 149 // Object which contains info related to the widget. The model_id property
150 150 // is used to identify the widget.
151 151 this.waitFor(function () {
152 152 var pending = this.evaluate(function (m) {
153 153 return IPython.notebook.kernel.widget_manager.get_model(m).pending_msgs;
154 154 }, {m: widget_info.model_id});
155 155
156 156 if (pending === 0) {
157 157 return true;
158 158 } else {
159 159 return false;
160 160 }
161 161 });
162 162 };
163 163
164 164 casper.get_output_cell = function (cell_num, out_num) {
165 165 // return an output of a given cell
166 166 out_num = out_num || 0;
167 167 var result = casper.evaluate(function (c, o) {
168 168 var cell = IPython.notebook.get_cell(c);
169 169 return cell.output_area.outputs[o];
170 170 },
171 171 {c : cell_num, o : out_num});
172 172 if (!result) {
173 173 var num_outputs = casper.evaluate(function (c) {
174 174 var cell = IPython.notebook.get_cell(c);
175 175 return cell.output_area.outputs.length;
176 176 },
177 177 {c : cell_num});
178 178 this.test.assertTrue(false,
179 179 "Cell " + cell_num + " has no output #" + out_num + " (" + num_outputs + " total)"
180 180 );
181 181 } else {
182 182 return result;
183 183 }
184 184 };
185 185
186 186 casper.get_cells_length = function () {
187 187 // return the number of cells in the notebook
188 188 var result = casper.evaluate(function () {
189 189 return IPython.notebook.get_cells().length;
190 190 });
191 191 return result;
192 192 };
193 193
194 194 casper.set_cell_text = function(index, text){
195 195 // Set the text content of a cell.
196 196 this.evaluate(function (index, text) {
197 197 var cell = IPython.notebook.get_cell(index);
198 198 cell.set_text(text);
199 199 }, index, text);
200 200 };
201 201
202 202 casper.get_cell_text = function(index){
203 203 // Get the text content of a cell.
204 204 return this.evaluate(function (index) {
205 205 var cell = IPython.notebook.get_cell(index);
206 206 return cell.get_text();
207 207 }, index);
208 208 };
209 209
210 210 casper.insert_cell_at_bottom = function(cell_type){
211 211 // Inserts a cell at the bottom of the notebook
212 212 // Returns the new cell's index.
213 213 return this.evaluate(function (cell_type) {
214 214 var cell = IPython.notebook.insert_cell_at_bottom(cell_type);
215 215 return IPython.notebook.find_cell_index(cell);
216 216 }, cell_type);
217 217 };
218 218
219 219 casper.append_cell = function(text, cell_type) {
220 220 // Insert a cell at the bottom of the notebook and set the cells text.
221 221 // Returns the new cell's index.
222 222 var index = this.insert_cell_at_bottom(cell_type);
223 223 if (text !== undefined) {
224 224 this.set_cell_text(index, text);
225 225 }
226 226 return index;
227 227 };
228 228
229 229 casper.execute_cell = function(index, expect_failure){
230 230 // Asynchronously executes a cell by index.
231 231 // Returns the cell's index.
232 232
233 233 if (expect_failure === undefined) expect_failure = false;
234 234 var that = this;
235 235 this.then(function(){
236 236 that.evaluate(function (index) {
237 237 var cell = IPython.notebook.get_cell(index);
238 238 cell.execute();
239 239 }, index);
240 240 });
241 241 this.wait_for_idle();
242 242
243 243 this.then(function () {
244 244 var error = that.evaluate(function (index) {
245 245 var cell = IPython.notebook.get_cell(index);
246 246 var outputs = cell.output_area.outputs;
247 247 for (var i = 0; i < outputs.length; i++) {
248 248 if (outputs[i].output_type == 'error') {
249 249 return outputs[i];
250 250 }
251 251 }
252 252 return false;
253 253 }, index);
254 254 if (error === null) {
255 255 this.test.fail("Failed to check for error output");
256 256 }
257 257 if (expect_failure && error === false) {
258 258 this.test.fail("Expected error while running cell");
259 259 } else if (!expect_failure && error !== false) {
260 260 this.test.fail("Error running cell:\n" + error.traceback.join('\n'));
261 261 }
262 262 });
263 263 return index;
264 264 };
265 265
266 266 casper.execute_cell_then = function(index, then_callback, expect_failure) {
267 267 // Synchronously executes a cell by index.
268 268 // Optionally accepts a then_callback parameter. then_callback will get called
269 269 // when the cell has finished executing.
270 270 // Returns the cell's index.
271 271 var return_val = this.execute_cell(index, expect_failure);
272 272
273 273 this.wait_for_idle();
274 274
275 275 var that = this;
276 276 this.then(function(){
277 277 if (then_callback!==undefined) {
278 278 then_callback.apply(that, [index]);
279 279 }
280 280 });
281 281
282 282 return return_val;
283 283 };
284 284
285 285 casper.cell_element_exists = function(index, selector){
286 286 // Utility function that allows us to easily check if an element exists
287 287 // within a cell. Uses JQuery selector to look for the element.
288 288 return casper.evaluate(function (index, selector) {
289 289 var $cell = IPython.notebook.get_cell(index).element;
290 290 return $cell.find(selector).length > 0;
291 291 }, index, selector);
292 292 };
293 293
294 294 casper.cell_element_function = function(index, selector, function_name, function_args){
295 295 // Utility function that allows us to execute a jQuery function on an
296 296 // element within a cell.
297 297 return casper.evaluate(function (index, selector, function_name, function_args) {
298 298 var $cell = IPython.notebook.get_cell(index).element;
299 299 var $el = $cell.find(selector);
300 300 return $el[function_name].apply($el, function_args);
301 301 }, index, selector, function_name, function_args);
302 302 };
303 303
304 304 casper.validate_notebook_state = function(message, mode, cell_index) {
305 305 // Validate the entire dual mode state of the notebook. Make sure no more than
306 306 // one cell is selected, focused, in edit mode, etc...
307 307
308 308 // General tests.
309 309 this.test.assertEquals(this.get_keyboard_mode(), this.get_notebook_mode(),
310 310 message + '; keyboard and notebook modes match');
311 311 // Is the selected cell the only cell that is selected?
312 312 if (cell_index!==undefined) {
313 313 this.test.assert(this.is_only_cell_selected(cell_index),
314 314 message + '; cell ' + cell_index + ' is the only cell selected');
315 315 }
316 316
317 317 // Mode specific tests.
318 318 if (mode==='command') {
319 319 // Are the notebook and keyboard manager in command mode?
320 320 this.test.assertEquals(this.get_keyboard_mode(), 'command',
321 321 message + '; in command mode');
322 322 // Make sure there isn't a single cell in edit mode.
323 323 this.test.assert(this.is_only_cell_edit(null),
324 324 message + '; all cells in command mode');
325 325 this.test.assert(this.is_cell_editor_focused(null),
326 326 message + '; no cell editors are focused while in command mode');
327 327
328 328 } else if (mode==='edit') {
329 329 // Are the notebook and keyboard manager in edit mode?
330 330 this.test.assertEquals(this.get_keyboard_mode(), 'edit',
331 331 message + '; in edit mode');
332 332 if (cell_index!==undefined) {
333 333 // Is the specified cell the only cell in edit mode?
334 334 this.test.assert(this.is_only_cell_edit(cell_index),
335 335 message + '; cell ' + cell_index + ' is the only cell in edit mode');
336 336 // Is the specified cell the only cell with a focused code mirror?
337 337 this.test.assert(this.is_cell_editor_focused(cell_index),
338 338 message + '; cell ' + cell_index + '\'s editor is appropriately focused');
339 339 }
340 340
341 341 } else {
342 342 this.test.assert(false, message + '; ' + mode + ' is an unknown mode');
343 343 }
344 344 };
345 345
346 346 casper.select_cell = function(index) {
347 347 // Select a cell in the notebook.
348 348 this.evaluate(function (i) {
349 349 IPython.notebook.select(i);
350 350 }, {i: index});
351 351 };
352 352
353 353 casper.click_cell_editor = function(index) {
354 354 // Emulate a click on a cell's editor.
355 355
356 356 // Code Mirror does not play nicely with emulated brower events.
357 357 // Instead of trying to emulate a click, here we run code similar to
358 358 // the code used in Code Mirror that handles the mousedown event on a
359 359 // region of codemirror that the user can focus.
360 360 this.evaluate(function (i) {
361 361 var cm = IPython.notebook.get_cell(i).code_mirror;
362 362 if (cm.options.readOnly != "nocursor" && (document.activeElement != cm.display.input))
363 363 cm.display.input.focus();
364 364 }, {i: index});
365 365 };
366 366
367 367 casper.set_cell_editor_cursor = function(index, line_index, char_index) {
368 368 // Set the Code Mirror instance cursor's location.
369 369 this.evaluate(function (i, l, c) {
370 370 IPython.notebook.get_cell(i).code_mirror.setCursor(l, c);
371 371 }, {i: index, l: line_index, c: char_index});
372 372 };
373 373
374 374 casper.focus_notebook = function() {
375 375 // Focus the notebook div.
376 376 this.evaluate(function (){
377 377 $('#notebook').focus();
378 378 }, {});
379 379 };
380 380
381 381 casper.trigger_keydown = function() {
382 382 // Emulate a keydown in the notebook.
383 383 for (var i = 0; i < arguments.length; i++) {
384 384 this.evaluate(function (k) {
385 385 var element = $(document);
386 386 var event = IPython.keyboard.shortcut_to_event(k, 'keydown');
387 387 element.trigger(event);
388 388 }, {k: arguments[i]});
389 389 }
390 390 };
391 391
392 392 casper.get_keyboard_mode = function() {
393 393 // Get the mode of the keyboard manager.
394 394 return this.evaluate(function() {
395 395 return IPython.keyboard_manager.mode;
396 396 }, {});
397 397 };
398 398
399 399 casper.get_notebook_mode = function() {
400 400 // Get the mode of the notebook.
401 401 return this.evaluate(function() {
402 402 return IPython.notebook.mode;
403 403 }, {});
404 404 };
405 405
406 406 casper.get_cell = function(index) {
407 407 // Get a single cell.
408 408 //
409 409 // Note: Handles to DOM elements stored in the cell will be useless once in
410 410 // CasperJS context.
411 411 return this.evaluate(function(i) {
412 412 var cell = IPython.notebook.get_cell(i);
413 413 if (cell) {
414 414 return cell;
415 415 }
416 416 return null;
417 417 }, {i : index});
418 418 };
419 419
420 420 casper.is_cell_editor_focused = function(index) {
421 421 // Make sure a cell's editor is the only editor focused on the page.
422 422 return this.evaluate(function(i) {
423 423 var focused_textarea = $('#notebook .CodeMirror-focused textarea');
424 424 if (focused_textarea.length > 1) { throw 'More than one Code Mirror editor is focused at once!'; }
425 425 if (i === null) {
426 426 return focused_textarea.length === 0;
427 427 } else {
428 428 var cell = IPython.notebook.get_cell(i);
429 429 if (cell) {
430 430 return cell.code_mirror.getInputField() == focused_textarea[0];
431 431 }
432 432 }
433 433 return false;
434 434 }, {i : index});
435 435 };
436 436
437 437 casper.is_only_cell_selected = function(index) {
438 438 // Check if a cell is the only cell selected.
439 439 // Pass null as the index to check if no cells are selected.
440 440 return this.is_only_cell_on(index, 'selected', 'unselected');
441 441 };
442 442
443 443 casper.is_only_cell_edit = function(index) {
444 444 // Check if a cell is the only cell in edit mode.
445 445 // Pass null as the index to check if all of the cells are in command mode.
446 446 return this.is_only_cell_on(index, 'edit_mode', 'command_mode');
447 447 };
448 448
449 449 casper.is_only_cell_on = function(i, on_class, off_class) {
450 450 // Check if a cell is the only cell with the `on_class` DOM class applied to it.
451 451 // All of the other cells are checked for the `off_class` DOM class.
452 452 // Pass null as the index to check if all of the cells have the `off_class`.
453 453 var cells_length = this.get_cells_length();
454 454 for (var j = 0; j < cells_length; j++) {
455 455 if (j === i) {
456 456 if (this.cell_has_class(j, off_class) || !this.cell_has_class(j, on_class)) {
457 457 return false;
458 458 }
459 459 } else {
460 460 if (!this.cell_has_class(j, off_class) || this.cell_has_class(j, on_class)) {
461 461 return false;
462 462 }
463 463 }
464 464 }
465 465 return true;
466 466 };
467 467
468 468 casper.cell_has_class = function(index, classes) {
469 469 // Check if a cell has a class.
470 470 return this.evaluate(function(i, c) {
471 471 var cell = IPython.notebook.get_cell(i);
472 472 if (cell) {
473 473 return cell.element.hasClass(c);
474 474 }
475 475 return false;
476 476 }, {i : index, c: classes});
477 477 };
478 478
479 479 casper.is_cell_rendered = function (index) {
480 480 return this.evaluate(function(i) {
481 481 return !!IPython.notebook.get_cell(i).rendered;
482 482 }, {i:index});
483 483 };
484 484
485 485 casper.assert_colors_equal = function (hex_color, local_color, msg) {
486 486 // Tests to see if two colors are equal.
487 487 //
488 488 // Parameters
489 489 // hex_color: string
490 490 // Hexadecimal color code, with or without preceeding hash character.
491 491 // local_color: string
492 492 // Local color representation. Can either be hexadecimal (default for
493 493 // phantom) or rgb (default for slimer).
494 494
495 495 // Remove parentheses, hashes, semi-colons, and space characters.
496 496 hex_color = hex_color.replace(/[\(\); #]/, '');
497 497 local_color = local_color.replace(/[\(\); #]/, '');
498 498
499 499 // If the local color is rgb, clean it up and replace
500 500 if (local_color.substr(0,3).toLowerCase() == 'rgb') {
501 501 components = local_color.substr(3).split(',');
502 502 local_color = '';
503 503 for (var i = 0; i < components.length; i++) {
504 504 var part = parseInt(components[i]).toString(16);
505 505 while (part.length < 2) part = '0' + part;
506 506 local_color += part;
507 507 }
508 508 }
509 509
510 510 this.test.assertEquals(hex_color.toUpperCase(), local_color.toUpperCase(), msg);
511 511 };
512 512
513 513 casper.notebook_test = function(test) {
514 514 // Wrap a notebook test to reduce boilerplate.
515 515 this.open_new_notebook();
516 516
517 517 // Echo whether or not we are running this test using SlimerJS
518 518 if (this.evaluate(function(){
519 519 return typeof InstallTrigger !== 'undefined'; // Firefox 1.0+
520 520 })) {
521 521 console.log('This test is running in SlimerJS.');
522 522 this.slimerjs = true;
523 523 }
524 524
525 525 // Make sure to remove the onbeforeunload callback. This callback is
526 526 // responsible for the "Are you sure you want to quit?" type messages.
527 527 // PhantomJS ignores these prompts, SlimerJS does not which causes hangs.
528 528 this.then(function(){
529 529 this.evaluate(function(){
530 530 window.onbeforeunload = function(){};
531 531 });
532 532 });
533 533
534 534 this.then(test);
535 535
536 536 // Kill the kernel and delete the notebook.
537 537 this.shutdown_current_kernel();
538 538 // This is still broken but shouldn't be a problem for now.
539 539 // this.delete_current_notebook();
540 540
541 541 // This is required to clean up the page we just finished with. If we don't call this
542 542 // casperjs will leak file descriptors of all the open WebSockets in that page. We
543 543 // have to set this.page=null so that next time casper.start runs, it will create a
544 544 // new page from scratch.
545 545 this.then(function () {
546 546 this.page.close();
547 547 this.page = null;
548 548 });
549 549
550 550 // Run the browser automation.
551 551 this.run(function() {
552 552 this.test.done();
553 553 });
554 554 };
555 555
556 556 casper.wait_for_dashboard = function () {
557 557 // Wait for the dashboard list to load.
558 558 casper.waitForSelector('.list_item');
559 559 };
560 560
561 561 casper.open_dashboard = function () {
562 562 // Start casper by opening the dashboard page.
563 563 var baseUrl = this.get_notebook_server();
564 564 this.start(baseUrl);
565 565 this.waitFor(this.page_loaded);
566 566 this.wait_for_dashboard();
567 567 };
568 568
569 569 casper.dashboard_test = function (test) {
570 570 // Open the dashboard page and run a test.
571 571 this.open_dashboard();
572 572 this.then(test);
573 573
574 574 this.then(function () {
575 575 this.page.close();
576 576 this.page = null;
577 577 });
578 578
579 579 // Run the browser automation.
580 580 this.run(function() {
581 581 this.test.done();
582 582 });
583 583 };
584 584
585 585 // note that this will only work for UNIQUE events -- if you want to
586 586 // listen for the same event twice, this will not work!
587 587 casper.event_test = function (name, events, action, timeout) {
588 588
589 589 // set up handlers to listen for each of the events
590 590 this.thenEvaluate(function (events) {
591 591 var make_handler = function (event) {
592 592 return function () {
593 593 IPython._events_triggered.push(event);
594 594 IPython.notebook.events.off(event, null, IPython._event_handlers[event]);
595 595 delete IPython._event_handlers[event];
596 596 };
597 597 };
598 598 IPython._event_handlers = {};
599 599 IPython._events_triggered = [];
600 600 for (var i=0; i < events.length; i++) {
601 601 IPython._event_handlers[events[i]] = make_handler(events[i]);
602 602 IPython.notebook.events.on(events[i], IPython._event_handlers[events[i]]);
603 603 }
604 604 }, [events]);
605 605
606 606 // execute the requested action
607 607 this.then(action);
608 608
609 609 // wait for all the events to be triggered
610 610 this.waitFor(function () {
611 611 return this.evaluate(function (events) {
612 612 return IPython._events_triggered.length >= events.length;
613 613 }, [events]);
614 614 }, undefined, undefined, timeout);
615 615
616 616 // test that the events were triggered in the proper order
617 617 this.then(function () {
618 618 var triggered = this.evaluate(function () {
619 619 return IPython._events_triggered;
620 620 });
621 621 var handlers = this.evaluate(function () {
622 622 return Object.keys(IPython._event_handlers);
623 623 });
624 624 this.test.assertEquals(triggered.length, events.length, name + ': ' + events.length + ' events were triggered');
625 625 this.test.assertEquals(handlers.length, 0, name + ': all handlers triggered');
626 626 for (var i=0; i < events.length; i++) {
627 627 this.test.assertEquals(triggered[i], events[i], name + ': ' + events[i] + ' was triggered');
628 628 }
629 629 });
630 630
631 631 // turn off any remaining event listeners
632 632 this.thenEvaluate(function () {
633 633 for (var event in IPython._event_handlers) {
634 634 IPython.notebook.events.off(event, null, IPython._event_handlers[event]);
635 635 delete IPython._event_handlers[event];
636 636 }
637 637 });
638 638 };
639 639
640 640 casper.options.waitTimeout=10000;
641 641 casper.on('waitFor.timeout', function onWaitForTimeout(timeout) {
642 642 this.echo("Timeout for " + casper.get_notebook_server());
643 643 this.echo("Is the notebook server running?");
644 644 });
645 645
646 646 casper.print_log = function () {
647 647 // Pass `console.log` calls from page JS to casper.
648 648 this.on('remote.message', function(msg) {
649 649 this.echo('Remote message caught: ' + msg);
650 650 });
651 651 };
652 652
653 653 casper.on("page.error", function onError(msg, trace) {
654 654 // show errors in the browser
655 655 this.echo("Page Error!");
656 656 for (var i = 0; i < trace.length; i++) {
657 657 var frame = trace[i];
658 658 var file = frame.file;
659 659 // shorten common phantomjs evaluate url
660 660 // this will have a different value on slimerjs
661 661 if (file === "phantomjs://webpage.evaluate()") {
662 662 file = "evaluate";
663 663 }
664 664 this.echo("line " + frame.line + " of " + file);
665 665 if (frame.function.length > 0) {
666 666 this.echo("in " + frame.function);
667 667 }
668 668 }
669 669 this.echo(msg);
670 670 });
671 671
672 672
673 673 casper.capture_log = function () {
674 674 // show captured errors
675 675 var captured_log = [];
676 676 var seen_errors = 0;
677 677 this.on('remote.message', function(msg) {
678 678 captured_log.push(msg);
679 679 });
680 680
681 681 this.test.on("test.done", function (result) {
682 682 // test.done runs per-file,
683 683 // but suiteResults is per-suite (directory)
684 684 var current_errors;
685 685 if (this.suiteResults) {
686 686 // casper 1.1 has suiteResults
687 687 current_errors = this.suiteResults.countErrors() + this.suiteResults.countFailed();
688 688 } else {
689 689 // casper 1.0 has testResults instead
690 690 current_errors = this.testResults.failed;
691 691 }
692 692
693 693 if (current_errors > seen_errors && captured_log.length > 0) {
694 694 casper.echo("\nCaptured console.log:");
695 695 for (var i = 0; i < captured_log.length; i++) {
696 696 casper.echo(" " + captured_log[i]);
697 697 }
698 698 }
699 699 seen_errors = current_errors;
700 700 captured_log = [];
701 701 });
702 702 };
703 703
704 704 casper.capture_log();
@@ -1,688 +1,699 b''
1 1 # -*- coding: utf-8 -*-
2 2 """IPython Test Process Controller
3 3
4 4 This module runs one or more subprocesses which will actually run the IPython
5 5 test suite.
6 6
7 7 """
8 8
9 9 # Copyright (c) IPython Development Team.
10 10 # Distributed under the terms of the Modified BSD License.
11 11
12 12 from __future__ import print_function
13 13
14 14 import argparse
15 15 import json
16 16 import multiprocessing.pool
17 17 import os
18 18 import shutil
19 19 import signal
20 20 import sys
21 21 import subprocess
22 22 import time
23 23 import re
24 24
25 25 from .iptest import (
26 26 have, test_group_names as py_test_group_names, test_sections, StreamCapturer,
27 27 test_for,
28 28 )
29 29 from IPython.utils.path import compress_user
30 30 from IPython.utils.py3compat import bytes_to_str
31 31 from IPython.utils.sysinfo import get_sys_info
32 32 from IPython.utils.tempdir import TemporaryDirectory
33 33 from IPython.utils.text import strip_ansi
34 34
35 35 try:
36 36 # Python >= 3.3
37 37 from subprocess import TimeoutExpired
38 38 def popen_wait(p, timeout):
39 39 return p.wait(timeout)
40 40 except ImportError:
41 41 class TimeoutExpired(Exception):
42 42 pass
43 43 def popen_wait(p, timeout):
44 44 """backport of Popen.wait from Python 3"""
45 45 for i in range(int(10 * timeout)):
46 46 if p.poll() is not None:
47 47 return
48 48 time.sleep(0.1)
49 49 if p.poll() is None:
50 50 raise TimeoutExpired
51 51
52 52 NOTEBOOK_SHUTDOWN_TIMEOUT = 10
53 53
54 54 class TestController(object):
55 55 """Run tests in a subprocess
56 56 """
57 57 #: str, IPython test suite to be executed.
58 58 section = None
59 59 #: list, command line arguments to be executed
60 60 cmd = None
61 61 #: dict, extra environment variables to set for the subprocess
62 62 env = None
63 63 #: list, TemporaryDirectory instances to clear up when the process finishes
64 64 dirs = None
65 65 #: subprocess.Popen instance
66 66 process = None
67 67 #: str, process stdout+stderr
68 68 stdout = None
69 69
70 70 def __init__(self):
71 71 self.cmd = []
72 72 self.env = {}
73 73 self.dirs = []
74 74
75 75 def setup(self):
76 76 """Create temporary directories etc.
77 77
78 78 This is only called when we know the test group will be run. Things
79 79 created here may be cleaned up by self.cleanup().
80 80 """
81 81 pass
82 82
83 83 def launch(self, buffer_output=False):
84 84 # print('*** ENV:', self.env) # dbg
85 85 # print('*** CMD:', self.cmd) # dbg
86 86 env = os.environ.copy()
87 87 env.update(self.env)
88 88 output = subprocess.PIPE if buffer_output else None
89 89 stdout = subprocess.STDOUT if buffer_output else None
90 90 self.process = subprocess.Popen(self.cmd, stdout=output,
91 91 stderr=stdout, env=env)
92 92
93 93 def wait(self):
94 94 self.stdout, _ = self.process.communicate()
95 95 return self.process.returncode
96 96
97 97 def print_extra_info(self):
98 98 """Print extra information about this test run.
99 99
100 100 If we're running in parallel and showing the concise view, this is only
101 101 called if the test group fails. Otherwise, it's called before the test
102 102 group is started.
103 103
104 104 The base implementation does nothing, but it can be overridden by
105 105 subclasses.
106 106 """
107 107 return
108 108
109 109 def cleanup_process(self):
110 110 """Cleanup on exit by killing any leftover processes."""
111 111 subp = self.process
112 112 if subp is None or (subp.poll() is not None):
113 113 return # Process doesn't exist, or is already dead.
114 114
115 115 try:
116 116 print('Cleaning up stale PID: %d' % subp.pid)
117 117 subp.kill()
118 118 except: # (OSError, WindowsError) ?
119 119 # This is just a best effort, if we fail or the process was
120 120 # really gone, ignore it.
121 121 pass
122 122 else:
123 123 for i in range(10):
124 124 if subp.poll() is None:
125 125 time.sleep(0.1)
126 126 else:
127 127 break
128 128
129 129 if subp.poll() is None:
130 130 # The process did not die...
131 131 print('... failed. Manual cleanup may be required.')
132 132
133 133 def cleanup(self):
134 134 "Kill process if it's still alive, and clean up temporary directories"
135 135 self.cleanup_process()
136 136 for td in self.dirs:
137 137 td.cleanup()
138 138
139 139 __del__ = cleanup
140 140
141 141
142 142 class PyTestController(TestController):
143 143 """Run Python tests using IPython.testing.iptest"""
144 144 #: str, Python command to execute in subprocess
145 145 pycmd = None
146 146
147 147 def __init__(self, section, options):
148 148 """Create new test runner."""
149 149 TestController.__init__(self)
150 150 self.section = section
151 151 # pycmd is put into cmd[2] in PyTestController.launch()
152 152 self.cmd = [sys.executable, '-c', None, section]
153 153 self.pycmd = "from IPython.testing.iptest import run_iptest; run_iptest()"
154 154 self.options = options
155 155
156 156 def setup(self):
157 157 ipydir = TemporaryDirectory()
158 158 self.dirs.append(ipydir)
159 159 self.env['IPYTHONDIR'] = ipydir.name
160 160 self.workingdir = workingdir = TemporaryDirectory()
161 161 self.dirs.append(workingdir)
162 162 self.env['IPTEST_WORKING_DIR'] = workingdir.name
163 163 # This means we won't get odd effects from our own matplotlib config
164 164 self.env['MPLCONFIGDIR'] = workingdir.name
165 165
166 166 # From options:
167 167 if self.options.xunit:
168 168 self.add_xunit()
169 169 if self.options.coverage:
170 170 self.add_coverage()
171 171 self.env['IPTEST_SUBPROC_STREAMS'] = self.options.subproc_streams
172 172 self.cmd.extend(self.options.extra_args)
173 173
174 174 @property
175 175 def will_run(self):
176 176 try:
177 177 return test_sections[self.section].will_run
178 178 except KeyError:
179 179 return True
180 180
181 181 def add_xunit(self):
182 182 xunit_file = os.path.abspath(self.section + '.xunit.xml')
183 183 self.cmd.extend(['--with-xunit', '--xunit-file', xunit_file])
184 184
185 185 def add_coverage(self):
186 186 try:
187 187 sources = test_sections[self.section].includes
188 188 except KeyError:
189 189 sources = ['IPython']
190 190
191 191 coverage_rc = ("[run]\n"
192 192 "data_file = {data_file}\n"
193 193 "source =\n"
194 194 " {source}\n"
195 195 ).format(data_file=os.path.abspath('.coverage.'+self.section),
196 196 source="\n ".join(sources))
197 197 config_file = os.path.join(self.workingdir.name, '.coveragerc')
198 198 with open(config_file, 'w') as f:
199 199 f.write(coverage_rc)
200 200
201 201 self.env['COVERAGE_PROCESS_START'] = config_file
202 202 self.pycmd = "import coverage; coverage.process_startup(); " + self.pycmd
203 203
204 204 def launch(self, buffer_output=False):
205 205 self.cmd[2] = self.pycmd
206 206 super(PyTestController, self).launch(buffer_output=buffer_output)
207 207
208 208
209 209 js_prefix = 'js/'
210 210
211 211 def get_js_test_dir():
212 212 import IPython.html.tests as t
213 213 return os.path.join(os.path.dirname(t.__file__), '')
214 214
215 215 def all_js_groups():
216 216 import glob
217 217 test_dir = get_js_test_dir()
218 218 all_subdirs = glob.glob(test_dir + '[!_]*/')
219 219 return [js_prefix+os.path.relpath(x, test_dir) for x in all_subdirs]
220 220
221 221 class JSController(TestController):
222 222 """Run CasperJS tests """
223
223 224 requirements = ['zmq', 'tornado', 'jinja2', 'casperjs', 'sqlite3',
224 225 'jsonschema']
225 226 display_slimer_output = False
226 227
227 def __init__(self, section, xunit=True, engine='phantomjs'):
228 def __init__(self, section, xunit=True, engine='phantomjs', url=None):
228 229 """Create new test runner."""
229 230 TestController.__init__(self)
230 231 self.engine = engine
231 232 self.section = section
232 233 self.xunit = xunit
234 self.url = url
233 235 self.slimer_failure = re.compile('^FAIL.*', flags=re.MULTILINE)
234 236 js_test_dir = get_js_test_dir()
235 237 includes = '--includes=' + os.path.join(js_test_dir,'util.js')
236 238 test_cases = os.path.join(js_test_dir, self.section[len(js_prefix):])
237 239 self.cmd = ['casperjs', 'test', includes, test_cases, '--engine=%s' % self.engine]
238 240
239 241 def setup(self):
240 242 self.ipydir = TemporaryDirectory()
241 243 self.nbdir = TemporaryDirectory()
242 244 self.dirs.append(self.ipydir)
243 245 self.dirs.append(self.nbdir)
244 246 os.makedirs(os.path.join(self.nbdir.name, os.path.join(u'sub βˆ‚ir1', u'sub βˆ‚ir 1a')))
245 247 os.makedirs(os.path.join(self.nbdir.name, os.path.join(u'sub βˆ‚ir2', u'sub βˆ‚ir 1b')))
246 248
247 249 if self.xunit:
248 250 self.add_xunit()
249 251
250 # start the ipython notebook, so we get the port number
251 self.server_port = 0
252 self._init_server()
253 if self.server_port:
254 self.cmd.append("--port=%i" % self.server_port)
252 # If a url was specified, use that for the testing.
253 if self.url:
254 self.cmd.append("--url=%s" % self.url)
255 255 else:
256 # don't launch tests if the server didn't start
257 self.cmd = [sys.executable, '-c', 'raise SystemExit(1)']
256 # start the ipython notebook, so we get the port number
257 self.server_port = 0
258 self._init_server()
259 if self.server_port:
260 self.cmd.append("--port=%i" % self.server_port)
261 else:
262 # don't launch tests if the server didn't start
263 self.cmd = [sys.executable, '-c', 'raise SystemExit(1)']
258 264
259 265 def add_xunit(self):
260 266 xunit_file = os.path.abspath(self.section.replace('/','.') + '.xunit.xml')
261 267 self.cmd.append('--xunit=%s' % xunit_file)
262 268
263 269 def launch(self, buffer_output):
264 270 # If the engine is SlimerJS, we need to buffer the output because
265 271 # SlimerJS does not support exit codes, so CasperJS always returns 0.
266 272 if self.engine == 'slimerjs' and not buffer_output:
267 273 self.display_slimer_output = True
268 274 return super(JSController, self).launch(buffer_output=True)
269 275
270 276 else:
271 277 return super(JSController, self).launch(buffer_output=buffer_output)
272 278
273 279 def wait(self, *pargs, **kwargs):
274 280 """Wait for the JSController to finish"""
275 281 ret = super(JSController, self).wait(*pargs, **kwargs)
276 282 # If this is a SlimerJS controller, check the captured stdout for
277 283 # errors. Otherwise, just return the return code.
278 284 if self.engine == 'slimerjs':
279 285 stdout = bytes_to_str(self.stdout)
280 286 if self.display_slimer_output:
281 287 print(stdout)
282 288 if ret != 0:
283 289 # This could still happen e.g. if it's stopped by SIGINT
284 290 return ret
285 291 return bool(self.slimer_failure.search(strip_ansi(stdout)))
286 292 else:
287 293 return ret
288 294
289 295 def print_extra_info(self):
290 296 print("Running tests with notebook directory %r" % self.nbdir.name)
291 297
292 298 @property
293 299 def will_run(self):
294 300 should_run = all(have[a] for a in self.requirements + [self.engine])
295 301 tornado4 = test_for('tornado.version_info', (4,0,0), callback=None)
296 302 if should_run and self.engine == 'phantomjs' and tornado4:
297 303 print("phantomjs cannot connect websockets to tornado 4", file=sys.stderr)
298 304 return False
299 305 return should_run
300 306
301 307 def _init_server(self):
302 308 "Start the notebook server in a separate process"
303 309 self.server_command = command = [sys.executable,
304 310 '-m', 'IPython.html',
305 311 '--no-browser',
306 312 '--ipython-dir', self.ipydir.name,
307 313 '--notebook-dir', self.nbdir.name,
308 314 ]
309 315 # ipc doesn't work on Windows, and darwin has crazy-long temp paths,
310 316 # which run afoul of ipc's maximum path length.
311 317 if sys.platform.startswith('linux'):
312 318 command.append('--KernelManager.transport=ipc')
313 319 self.stream_capturer = c = StreamCapturer()
314 320 c.start()
315 321 self.server = subprocess.Popen(command, stdout=c.writefd, stderr=subprocess.STDOUT, cwd=self.nbdir.name)
316 322 self.server_info_file = os.path.join(self.ipydir.name,
317 323 'profile_default', 'security', 'nbserver-%i.json' % self.server.pid
318 324 )
319 325 self._wait_for_server()
320 326
321 327 def _wait_for_server(self):
322 328 """Wait 30 seconds for the notebook server to start"""
323 329 for i in range(300):
324 330 if self.server.poll() is not None:
325 331 return self._failed_to_start()
326 332 if os.path.exists(self.server_info_file):
327 333 try:
328 334 self._load_server_info()
329 335 except ValueError:
330 336 # If the server is halfway through writing the file, we may
331 337 # get invalid JSON; it should be ready next iteration.
332 338 pass
333 339 else:
334 340 return
335 341 time.sleep(0.1)
336 342 print("Notebook server-info file never arrived: %s" % self.server_info_file,
337 343 file=sys.stderr
338 344 )
339 345
340 346 def _failed_to_start(self):
341 347 """Notebook server exited prematurely"""
342 348 captured = self.stream_capturer.get_buffer().decode('utf-8', 'replace')
343 349 print("Notebook failed to start: ", file=sys.stderr)
344 350 print(self.server_command)
345 351 print(captured, file=sys.stderr)
346 352
347 353 def _load_server_info(self):
348 354 """Notebook server started, load connection info from JSON"""
349 355 with open(self.server_info_file) as f:
350 356 info = json.load(f)
351 357 self.server_port = info['port']
352 358
353 359 def cleanup(self):
354 360 try:
355 361 self.server.terminate()
356 362 except OSError:
357 363 # already dead
358 364 pass
359 365 # wait 10s for the server to shutdown
360 366 try:
361 367 popen_wait(self.server, NOTEBOOK_SHUTDOWN_TIMEOUT)
362 368 except TimeoutExpired:
363 369 # server didn't terminate, kill it
364 370 try:
365 371 print("Failed to terminate notebook server, killing it.",
366 372 file=sys.stderr
367 373 )
368 374 self.server.kill()
369 375 except OSError:
370 376 # already dead
371 377 pass
372 378 # wait another 10s
373 379 try:
374 380 popen_wait(self.server, NOTEBOOK_SHUTDOWN_TIMEOUT)
375 381 except TimeoutExpired:
376 382 print("Notebook server still running (%s)" % self.server_info_file,
377 383 file=sys.stderr
378 384 )
379 385
380 386 self.stream_capturer.halt()
381 387 TestController.cleanup(self)
382 388
383 389
384 390 def prepare_controllers(options):
385 391 """Returns two lists of TestController instances, those to run, and those
386 392 not to run."""
387 393 testgroups = options.testgroups
388 394 if testgroups:
389 395 if 'js' in testgroups:
390 396 js_testgroups = all_js_groups()
391 397 else:
392 398 js_testgroups = [g for g in testgroups if g.startswith(js_prefix)]
393 399
394 400 py_testgroups = [g for g in testgroups if g not in ['js'] + js_testgroups]
395 401 else:
396 402 py_testgroups = py_test_group_names
397 403 if not options.all:
398 404 js_testgroups = []
399 405 test_sections['parallel'].enabled = False
400 406 else:
401 407 js_testgroups = all_js_groups()
402 408
403 409 engine = 'slimerjs' if options.slimerjs else 'phantomjs'
404 c_js = [JSController(name, xunit=options.xunit, engine=engine) for name in js_testgroups]
410 c_js = [JSController(name, xunit=options.xunit, engine=engine, url=options.url) for name in js_testgroups]
405 411 c_py = [PyTestController(name, options) for name in py_testgroups]
406 412
407 413 controllers = c_py + c_js
408 414 to_run = [c for c in controllers if c.will_run]
409 415 not_run = [c for c in controllers if not c.will_run]
410 416 return to_run, not_run
411 417
412 418 def do_run(controller, buffer_output=True):
413 419 """Setup and run a test controller.
414 420
415 421 If buffer_output is True, no output is displayed, to avoid it appearing
416 422 interleaved. In this case, the caller is responsible for displaying test
417 423 output on failure.
418 424
419 425 Returns
420 426 -------
421 427 controller : TestController
422 428 The same controller as passed in, as a convenience for using map() type
423 429 APIs.
424 430 exitcode : int
425 431 The exit code of the test subprocess. Non-zero indicates failure.
426 432 """
427 433 try:
428 434 try:
429 435 controller.setup()
430 436 if not buffer_output:
431 437 controller.print_extra_info()
432 438 controller.launch(buffer_output=buffer_output)
433 439 except Exception:
434 440 import traceback
435 441 traceback.print_exc()
436 442 return controller, 1 # signal failure
437 443
438 444 exitcode = controller.wait()
439 445 return controller, exitcode
440 446
441 447 except KeyboardInterrupt:
442 448 return controller, -signal.SIGINT
443 449 finally:
444 450 controller.cleanup()
445 451
446 452 def report():
447 453 """Return a string with a summary report of test-related variables."""
448 454 inf = get_sys_info()
449 455 out = []
450 456 def _add(name, value):
451 457 out.append((name, value))
452 458
453 459 _add('IPython version', inf['ipython_version'])
454 460 _add('IPython commit', "{} ({})".format(inf['commit_hash'], inf['commit_source']))
455 461 _add('IPython package', compress_user(inf['ipython_path']))
456 462 _add('Python version', inf['sys_version'].replace('\n',''))
457 463 _add('sys.executable', compress_user(inf['sys_executable']))
458 464 _add('Platform', inf['platform'])
459 465
460 466 width = max(len(n) for (n,v) in out)
461 467 out = ["{:<{width}}: {}\n".format(n, v, width=width) for (n,v) in out]
462 468
463 469 avail = []
464 470 not_avail = []
465 471
466 472 for k, is_avail in have.items():
467 473 if is_avail:
468 474 avail.append(k)
469 475 else:
470 476 not_avail.append(k)
471 477
472 478 if avail:
473 479 out.append('\nTools and libraries available at test time:\n')
474 480 avail.sort()
475 481 out.append(' ' + ' '.join(avail)+'\n')
476 482
477 483 if not_avail:
478 484 out.append('\nTools and libraries NOT available at test time:\n')
479 485 not_avail.sort()
480 486 out.append(' ' + ' '.join(not_avail)+'\n')
481 487
482 488 return ''.join(out)
483 489
484 490 def run_iptestall(options):
485 491 """Run the entire IPython test suite by calling nose and trial.
486 492
487 493 This function constructs :class:`IPTester` instances for all IPython
488 494 modules and package and then runs each of them. This causes the modules
489 495 and packages of IPython to be tested each in their own subprocess using
490 496 nose.
491 497
492 498 Parameters
493 499 ----------
494 500
495 501 All parameters are passed as attributes of the options object.
496 502
497 503 testgroups : list of str
498 504 Run only these sections of the test suite. If empty, run all the available
499 505 sections.
500 506
501 507 fast : int or None
502 508 Run the test suite in parallel, using n simultaneous processes. If None
503 509 is passed, one process is used per CPU core. Default 1 (i.e. sequential)
504 510
505 511 inc_slow : bool
506 512 Include slow tests, like IPython.parallel. By default, these tests aren't
507 513 run.
508 514
509 515 slimerjs : bool
510 516 Use slimerjs if it's installed instead of phantomjs for casperjs tests.
511 517
518 url : unicode
519 Address:port to use when running the JS tests.
520
512 521 xunit : bool
513 522 Produce Xunit XML output. This is written to multiple foo.xunit.xml files.
514 523
515 524 coverage : bool or str
516 525 Measure code coverage from tests. True will store the raw coverage data,
517 526 or pass 'html' or 'xml' to get reports.
518 527
519 528 extra_args : list
520 529 Extra arguments to pass to the test subprocesses, e.g. '-v'
521 530 """
522 531 to_run, not_run = prepare_controllers(options)
523 532
524 533 def justify(ltext, rtext, width=70, fill='-'):
525 534 ltext += ' '
526 535 rtext = (' ' + rtext).rjust(width - len(ltext), fill)
527 536 return ltext + rtext
528 537
529 538 # Run all test runners, tracking execution time
530 539 failed = []
531 540 t_start = time.time()
532 541
533 542 print()
534 543 if options.fast == 1:
535 544 # This actually means sequential, i.e. with 1 job
536 545 for controller in to_run:
537 546 print('Test group:', controller.section)
538 547 sys.stdout.flush() # Show in correct order when output is piped
539 548 controller, res = do_run(controller, buffer_output=False)
540 549 if res:
541 550 failed.append(controller)
542 551 if res == -signal.SIGINT:
543 552 print("Interrupted")
544 553 break
545 554 print()
546 555
547 556 else:
548 557 # Run tests concurrently
549 558 try:
550 559 pool = multiprocessing.pool.ThreadPool(options.fast)
551 560 for (controller, res) in pool.imap_unordered(do_run, to_run):
552 561 res_string = 'OK' if res == 0 else 'FAILED'
553 562 print(justify('Test group: ' + controller.section, res_string))
554 563 if res:
555 564 controller.print_extra_info()
556 565 print(bytes_to_str(controller.stdout))
557 566 failed.append(controller)
558 567 if res == -signal.SIGINT:
559 568 print("Interrupted")
560 569 break
561 570 except KeyboardInterrupt:
562 571 return
563 572
564 573 for controller in not_run:
565 574 print(justify('Test group: ' + controller.section, 'NOT RUN'))
566 575
567 576 t_end = time.time()
568 577 t_tests = t_end - t_start
569 578 nrunners = len(to_run)
570 579 nfail = len(failed)
571 580 # summarize results
572 581 print('_'*70)
573 582 print('Test suite completed for system with the following information:')
574 583 print(report())
575 584 took = "Took %.3fs." % t_tests
576 585 print('Status: ', end='')
577 586 if not failed:
578 587 print('OK (%d test groups).' % nrunners, took)
579 588 else:
580 589 # If anything went wrong, point out what command to rerun manually to
581 590 # see the actual errors and individual summary
582 591 failed_sections = [c.section for c in failed]
583 592 print('ERROR - {} out of {} test groups failed ({}).'.format(nfail,
584 593 nrunners, ', '.join(failed_sections)), took)
585 594 print()
586 595 print('You may wish to rerun these, with:')
587 596 print(' iptest', *failed_sections)
588 597 print()
589 598
590 599 if options.coverage:
591 600 from coverage import coverage
592 601 cov = coverage(data_file='.coverage')
593 602 cov.combine()
594 603 cov.save()
595 604
596 605 # Coverage HTML report
597 606 if options.coverage == 'html':
598 607 html_dir = 'ipy_htmlcov'
599 608 shutil.rmtree(html_dir, ignore_errors=True)
600 609 print("Writing HTML coverage report to %s/ ... " % html_dir, end="")
601 610 sys.stdout.flush()
602 611
603 612 # Custom HTML reporter to clean up module names.
604 613 from coverage.html import HtmlReporter
605 614 class CustomHtmlReporter(HtmlReporter):
606 615 def find_code_units(self, morfs):
607 616 super(CustomHtmlReporter, self).find_code_units(morfs)
608 617 for cu in self.code_units:
609 618 nameparts = cu.name.split(os.sep)
610 619 if 'IPython' not in nameparts:
611 620 continue
612 621 ix = nameparts.index('IPython')
613 622 cu.name = '.'.join(nameparts[ix:])
614 623
615 624 # Reimplement the html_report method with our custom reporter
616 625 cov._harvest_data()
617 626 cov.config.from_args(omit='*{0}tests{0}*'.format(os.sep), html_dir=html_dir,
618 627 html_title='IPython test coverage',
619 628 )
620 629 reporter = CustomHtmlReporter(cov, cov.config)
621 630 reporter.report(None)
622 631 print('done.')
623 632
624 633 # Coverage XML report
625 634 elif options.coverage == 'xml':
626 635 cov.xml_report(outfile='ipy_coverage.xml')
627 636
628 637 if failed:
629 638 # Ensure that our exit code indicates failure
630 639 sys.exit(1)
631 640
632 641 argparser = argparse.ArgumentParser(description='Run IPython test suite')
633 642 argparser.add_argument('testgroups', nargs='*',
634 643 help='Run specified groups of tests. If omitted, run '
635 644 'all tests.')
636 645 argparser.add_argument('--all', action='store_true',
637 646 help='Include slow tests not run by default.')
638 647 argparser.add_argument('--slimerjs', action='store_true',
639 648 help="Use slimerjs if it's installed instead of phantomjs for casperjs tests.")
649 argparser.add_argument('--url', const=None, default='', type=unicode,
650 help="URL to use for the JS tests.")
640 651 argparser.add_argument('-j', '--fast', nargs='?', const=None, default=1, type=int,
641 652 help='Run test sections in parallel. This starts as many '
642 653 'processes as you have cores, or you can specify a number.')
643 654 argparser.add_argument('--xunit', action='store_true',
644 655 help='Produce Xunit XML results')
645 656 argparser.add_argument('--coverage', nargs='?', const=True, default=False,
646 657 help="Measure test coverage. Specify 'html' or "
647 658 "'xml' to get reports.")
648 659 argparser.add_argument('--subproc-streams', default='capture',
649 660 help="What to do with stdout/stderr from subprocesses. "
650 661 "'capture' (default), 'show' and 'discard' are the options.")
651 662
652 663 def default_options():
653 664 """Get an argparse Namespace object with the default arguments, to pass to
654 665 :func:`run_iptestall`.
655 666 """
656 667 options = argparser.parse_args([])
657 668 options.extra_args = []
658 669 return options
659 670
660 671 def main():
661 672 # iptest doesn't work correctly if the working directory is the
662 673 # root of the IPython source tree. Tell the user to avoid
663 674 # frustration.
664 675 if os.path.exists(os.path.join(os.getcwd(),
665 676 'IPython', 'testing', '__main__.py')):
666 677 print("Don't run iptest from the IPython source directory",
667 678 file=sys.stderr)
668 679 sys.exit(1)
669 680 # Arguments after -- should be passed through to nose. Argparse treats
670 681 # everything after -- as regular positional arguments, so we separate them
671 682 # first.
672 683 try:
673 684 ix = sys.argv.index('--')
674 685 except ValueError:
675 686 to_parse = sys.argv[1:]
676 687 extra_args = []
677 688 else:
678 689 to_parse = sys.argv[1:ix]
679 690 extra_args = sys.argv[ix+1:]
680 691
681 692 options = argparser.parse_args(to_parse)
682 693 options.extra_args = extra_args
683 694
684 695 run_iptestall(options)
685 696
686 697
687 698 if __name__ == '__main__':
688 699 main()
General Comments 0
You need to be logged in to leave comments. Login now