Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/extensions/ipynb/src/test/clearOutputs.test.ts
3292 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import * as sinon from 'sinon';
7
import type * as nbformat from '@jupyterlab/nbformat';
8
import * as assert from 'assert';
9
import * as vscode from 'vscode';
10
import { jupyterNotebookModelToNotebookData } from '../deserializers';
11
import { activate } from '../notebookModelStoreSync';
12
13
14
suite(`ipynb Clear Outputs`, () => {
15
const disposables: vscode.Disposable[] = [];
16
const context = { subscriptions: disposables } as vscode.ExtensionContext;
17
setup(() => {
18
disposables.length = 0;
19
activate(context);
20
});
21
teardown(async () => {
22
disposables.forEach(d => d.dispose());
23
disposables.length = 0;
24
sinon.restore();
25
await vscode.commands.executeCommand('workbench.action.closeAllEditors');
26
});
27
28
test.skip('Clear outputs after opening Notebook', async () => {
29
const cells: nbformat.ICell[] = [
30
{
31
cell_type: 'code',
32
execution_count: 10,
33
outputs: [{ output_type: 'stream', name: 'stdout', text: ['Hello'] }],
34
source: 'print(1)',
35
metadata: {}
36
},
37
{
38
cell_type: 'code',
39
outputs: [],
40
source: 'print(2)',
41
metadata: {}
42
},
43
{
44
cell_type: 'markdown',
45
source: '# HEAD',
46
metadata: {}
47
}
48
];
49
const notebook = jupyterNotebookModelToNotebookData({ cells }, 'python');
50
51
const notebookDocumentPromise = vscode.workspace.openNotebookDocument('jupyter-notebook', notebook);
52
await raceTimeout(notebookDocumentPromise, 5000, () => {
53
throw new Error('Timeout waiting for notebook to open');
54
});
55
const notebookDocument = await notebookDocumentPromise;
56
await raceTimeout(vscode.window.showNotebookDocument(notebookDocument), 20000, () => {
57
throw new Error('Timeout waiting for notebook to open');
58
});
59
60
assert.strictEqual(notebookDocument.cellCount, 3);
61
assert.strictEqual(notebookDocument.cellAt(0).metadata.execution_count, 10);
62
assert.strictEqual(notebookDocument.cellAt(1).metadata.execution_count, null);
63
assert.strictEqual(notebookDocument.cellAt(2).metadata.execution_count, undefined);
64
65
// Clear all outputs
66
await raceTimeout(vscode.commands.executeCommand('notebook.clearAllCellsOutputs'), 5000, () => {
67
throw new Error('Timeout waiting for notebook to clear outputs');
68
});
69
70
// Wait for all changes to be applied, could take a few ms.
71
const verifyMetadataChanges = () => {
72
assert.strictEqual(notebookDocument.cellAt(0).metadata.execution_count, null);
73
assert.strictEqual(notebookDocument.cellAt(1).metadata.execution_count, null);
74
assert.strictEqual(notebookDocument.cellAt(2).metadata.execution_count, undefined);
75
};
76
77
vscode.workspace.onDidChangeNotebookDocument(() => verifyMetadataChanges(), undefined, disposables);
78
79
await new Promise<void>((resolve, reject) => {
80
const interval = setInterval(() => {
81
try {
82
verifyMetadataChanges();
83
clearInterval(interval);
84
resolve();
85
} catch {
86
// Ignore
87
}
88
}, 50);
89
disposables.push({ dispose: () => clearInterval(interval) });
90
const timeout = setTimeout(() => {
91
try {
92
verifyMetadataChanges();
93
resolve();
94
} catch (ex) {
95
reject(ex);
96
}
97
}, 1000);
98
disposables.push({ dispose: () => clearTimeout(timeout) });
99
});
100
});
101
102
103
// test('Serialize', async () => {
104
// const markdownCell = new vscode.NotebookCellData(vscode.NotebookCellKind.Markup, '# header1', 'markdown');
105
// markdownCell.metadata = {
106
// attachments: {
107
// 'image.png': {
108
// 'image/png': 'abc'
109
// }
110
// },
111
// id: '123',
112
// metadata: {
113
// foo: 'bar'
114
// }
115
// };
116
117
// const cellMetadata = getCellMetadata({ cell: markdownCell });
118
// assert.deepStrictEqual(cellMetadata, {
119
// id: '123',
120
// metadata: {
121
// foo: 'bar',
122
// },
123
// attachments: {
124
// 'image.png': {
125
// 'image/png': 'abc'
126
// }
127
// }
128
// });
129
130
// const markdownCell2 = new vscode.NotebookCellData(vscode.NotebookCellKind.Markup, '# header1', 'markdown');
131
// markdownCell2.metadata = {
132
// id: '123',
133
// metadata: {
134
// foo: 'bar'
135
// },
136
// attachments: {
137
// 'image.png': {
138
// 'image/png': 'abc'
139
// }
140
// }
141
// };
142
143
// const nbMarkdownCell = createMarkdownCellFromNotebookCell(markdownCell);
144
// const nbMarkdownCell2 = createMarkdownCellFromNotebookCell(markdownCell2);
145
// assert.deepStrictEqual(nbMarkdownCell, nbMarkdownCell2);
146
147
// assert.deepStrictEqual(nbMarkdownCell, {
148
// cell_type: 'markdown',
149
// source: ['# header1'],
150
// metadata: {
151
// foo: 'bar',
152
// },
153
// attachments: {
154
// 'image.png': {
155
// 'image/png': 'abc'
156
// }
157
// },
158
// id: '123'
159
// });
160
// });
161
162
// suite('Outputs', () => {
163
// function validateCellOutputTranslation(
164
// outputs: nbformat.IOutput[],
165
// expectedOutputs: vscode.NotebookCellOutput[],
166
// propertiesToExcludeFromComparison: string[] = []
167
// ) {
168
// const cells: nbformat.ICell[] = [
169
// {
170
// cell_type: 'code',
171
// execution_count: 10,
172
// outputs,
173
// source: 'print(1)',
174
// metadata: {}
175
// }
176
// ];
177
// const notebook = jupyterNotebookModelToNotebookData({ cells }, 'python');
178
179
// // OutputItems contain an `id` property generated by VSC.
180
// // Exclude that property when comparing.
181
// const propertiesToExclude = propertiesToExcludeFromComparison.concat(['id']);
182
// const actualOuts = notebook.cells[0].outputs;
183
// deepStripProperties(actualOuts, propertiesToExclude);
184
// deepStripProperties(expectedOutputs, propertiesToExclude);
185
// assert.deepStrictEqual(actualOuts, expectedOutputs);
186
// }
187
188
// test('Empty output', () => {
189
// validateCellOutputTranslation([], []);
190
// });
191
192
// test('Stream output', () => {
193
// validateCellOutputTranslation(
194
// [
195
// {
196
// output_type: 'stream',
197
// name: 'stderr',
198
// text: 'Error'
199
// },
200
// {
201
// output_type: 'stream',
202
// name: 'stdout',
203
// text: 'NoError'
204
// }
205
// ],
206
// [
207
// new vscode.NotebookCellOutput([vscode.NotebookCellOutputItem.stderr('Error')], {
208
// outputType: 'stream'
209
// }),
210
// new vscode.NotebookCellOutput([vscode.NotebookCellOutputItem.stdout('NoError')], {
211
// outputType: 'stream'
212
// })
213
// ]
214
// );
215
// });
216
// test('Stream output and line endings', () => {
217
// validateCellOutputTranslation(
218
// [
219
// {
220
// output_type: 'stream',
221
// name: 'stdout',
222
// text: [
223
// 'Line1\n',
224
// '\n',
225
// 'Line3\n',
226
// 'Line4'
227
// ]
228
// }
229
// ],
230
// [
231
// new vscode.NotebookCellOutput([vscode.NotebookCellOutputItem.stdout('Line1\n\nLine3\nLine4')], {
232
// outputType: 'stream'
233
// })
234
// ]
235
// );
236
// validateCellOutputTranslation(
237
// [
238
// {
239
// output_type: 'stream',
240
// name: 'stdout',
241
// text: [
242
// 'Hello\n',
243
// 'Hello\n',
244
// 'Hello\n',
245
// 'Hello\n',
246
// 'Hello\n',
247
// 'Hello\n'
248
// ]
249
// }
250
// ],
251
// [
252
// new vscode.NotebookCellOutput([vscode.NotebookCellOutputItem.stdout('Hello\nHello\nHello\nHello\nHello\nHello\n')], {
253
// outputType: 'stream'
254
// })
255
// ]
256
// );
257
// });
258
// test('Multi-line Stream output', () => {
259
// validateCellOutputTranslation(
260
// [
261
// {
262
// name: 'stdout',
263
// output_type: 'stream',
264
// text: [
265
// 'Epoch 1/5\n',
266
// '...\n',
267
// 'Epoch 2/5\n',
268
// '...\n',
269
// 'Epoch 3/5\n',
270
// '...\n',
271
// 'Epoch 4/5\n',
272
// '...\n',
273
// 'Epoch 5/5\n',
274
// '...\n'
275
// ]
276
// }
277
// ],
278
// [
279
// new vscode.NotebookCellOutput([vscode.NotebookCellOutputItem.stdout(['Epoch 1/5\n',
280
// '...\n',
281
// 'Epoch 2/5\n',
282
// '...\n',
283
// 'Epoch 3/5\n',
284
// '...\n',
285
// 'Epoch 4/5\n',
286
// '...\n',
287
// 'Epoch 5/5\n',
288
// '...\n'].join(''))], {
289
// outputType: 'stream'
290
// })
291
// ]
292
// );
293
// });
294
295
// test('Multi-line Stream output (last empty line should not be saved in ipynb)', () => {
296
// validateCellOutputTranslation(
297
// [
298
// {
299
// name: 'stderr',
300
// output_type: 'stream',
301
// text: [
302
// 'Epoch 1/5\n',
303
// '...\n',
304
// 'Epoch 2/5\n',
305
// '...\n',
306
// 'Epoch 3/5\n',
307
// '...\n',
308
// 'Epoch 4/5\n',
309
// '...\n',
310
// 'Epoch 5/5\n',
311
// '...\n'
312
// ]
313
// }
314
// ],
315
// [
316
// new vscode.NotebookCellOutput([vscode.NotebookCellOutputItem.stderr(['Epoch 1/5\n',
317
// '...\n',
318
// 'Epoch 2/5\n',
319
// '...\n',
320
// 'Epoch 3/5\n',
321
// '...\n',
322
// 'Epoch 4/5\n',
323
// '...\n',
324
// 'Epoch 5/5\n',
325
// '...\n',
326
// // This last empty line should not be saved in ipynb.
327
// '\n'].join(''))], {
328
// outputType: 'stream'
329
// })
330
// ]
331
// );
332
// });
333
334
// test('Streamed text with Ansi characters', async () => {
335
// validateCellOutputTranslation(
336
// [
337
// {
338
// name: 'stderr',
339
// text: '\u001b[K\u001b[33m✅ \u001b[0m Loading\n',
340
// output_type: 'stream'
341
// }
342
// ],
343
// [
344
// new vscode.NotebookCellOutput(
345
// [vscode.NotebookCellOutputItem.stderr('\u001b[K\u001b[33m✅ \u001b[0m Loading\n')],
346
// {
347
// outputType: 'stream'
348
// }
349
// )
350
// ]
351
// );
352
// });
353
354
// test('Streamed text with angle bracket characters', async () => {
355
// validateCellOutputTranslation(
356
// [
357
// {
358
// name: 'stderr',
359
// text: '1 is < 2',
360
// output_type: 'stream'
361
// }
362
// ],
363
// [
364
// new vscode.NotebookCellOutput([vscode.NotebookCellOutputItem.stderr('1 is < 2')], {
365
// outputType: 'stream'
366
// })
367
// ]
368
// );
369
// });
370
371
// test('Streamed text with angle bracket characters and ansi chars', async () => {
372
// validateCellOutputTranslation(
373
// [
374
// {
375
// name: 'stderr',
376
// text: '1 is < 2\u001b[K\u001b[33m✅ \u001b[0m Loading\n',
377
// output_type: 'stream'
378
// }
379
// ],
380
// [
381
// new vscode.NotebookCellOutput(
382
// [vscode.NotebookCellOutputItem.stderr('1 is < 2\u001b[K\u001b[33m✅ \u001b[0m Loading\n')],
383
// {
384
// outputType: 'stream'
385
// }
386
// )
387
// ]
388
// );
389
// });
390
391
// test('Error', async () => {
392
// validateCellOutputTranslation(
393
// [
394
// {
395
// ename: 'Error Name',
396
// evalue: 'Error Value',
397
// traceback: ['stack1', 'stack2', 'stack3'],
398
// output_type: 'error'
399
// }
400
// ],
401
// [
402
// new vscode.NotebookCellOutput(
403
// [
404
// vscode.NotebookCellOutputItem.error({
405
// name: 'Error Name',
406
// message: 'Error Value',
407
// stack: ['stack1', 'stack2', 'stack3'].join('\n')
408
// })
409
// ],
410
// {
411
// outputType: 'error',
412
// originalError: {
413
// ename: 'Error Name',
414
// evalue: 'Error Value',
415
// traceback: ['stack1', 'stack2', 'stack3'],
416
// output_type: 'error'
417
// }
418
// }
419
// )
420
// ]
421
// );
422
// });
423
424
// ['display_data', 'execute_result'].forEach(output_type => {
425
// suite(`Rich output for output_type = ${output_type}`, () => {
426
// // Properties to exclude when comparing.
427
// let propertiesToExcludeFromComparison: string[] = [];
428
// setup(() => {
429
// if (output_type === 'display_data') {
430
// // With display_data the execution_count property will never exist in the output.
431
// // We can ignore that (as it will never exist).
432
// // But we leave it in the case of `output_type === 'execute_result'`
433
// propertiesToExcludeFromComparison = ['execution_count', 'executionCount'];
434
// }
435
// });
436
437
// test('Text mimeType output', async () => {
438
// validateCellOutputTranslation(
439
// [
440
// {
441
// data: {
442
// 'text/plain': 'Hello World!'
443
// },
444
// output_type,
445
// metadata: {},
446
// execution_count: 1
447
// }
448
// ],
449
// [
450
// new vscode.NotebookCellOutput(
451
// [new vscode.NotebookCellOutputItem(Buffer.from('Hello World!', 'utf8'), 'text/plain')],
452
// {
453
// outputType: output_type,
454
// metadata: {}, // display_data & execute_result always have metadata.
455
// executionCount: 1
456
// }
457
// )
458
// ],
459
// propertiesToExcludeFromComparison
460
// );
461
// });
462
463
// test('png,jpeg images', async () => {
464
// validateCellOutputTranslation(
465
// [
466
// {
467
// execution_count: 1,
468
// data: {
469
// 'image/png': base64EncodedImage,
470
// 'image/jpeg': base64EncodedImage
471
// },
472
// metadata: {},
473
// output_type
474
// }
475
// ],
476
// [
477
// new vscode.NotebookCellOutput(
478
// [
479
// new vscode.NotebookCellOutputItem(Buffer.from(base64EncodedImage, 'base64'), 'image/png'),
480
// new vscode.NotebookCellOutputItem(Buffer.from(base64EncodedImage, 'base64'), 'image/jpeg')
481
// ],
482
// {
483
// executionCount: 1,
484
// outputType: output_type,
485
// metadata: {} // display_data & execute_result always have metadata.
486
// }
487
// )
488
// ],
489
// propertiesToExcludeFromComparison
490
// );
491
// });
492
493
// test('png image with a light background', async () => {
494
// validateCellOutputTranslation(
495
// [
496
// {
497
// execution_count: 1,
498
// data: {
499
// 'image/png': base64EncodedImage
500
// },
501
// metadata: {
502
// needs_background: 'light'
503
// },
504
// output_type
505
// }
506
// ],
507
// [
508
// new vscode.NotebookCellOutput(
509
// [new vscode.NotebookCellOutputItem(Buffer.from(base64EncodedImage, 'base64'), 'image/png')],
510
// {
511
// executionCount: 1,
512
// metadata: {
513
// needs_background: 'light'
514
// },
515
// outputType: output_type
516
// }
517
// )
518
// ],
519
// propertiesToExcludeFromComparison
520
// );
521
// });
522
523
// test('png image with a dark background', async () => {
524
// validateCellOutputTranslation(
525
// [
526
// {
527
// execution_count: 1,
528
// data: {
529
// 'image/png': base64EncodedImage
530
// },
531
// metadata: {
532
// needs_background: 'dark'
533
// },
534
// output_type
535
// }
536
// ],
537
// [
538
// new vscode.NotebookCellOutput(
539
// [new vscode.NotebookCellOutputItem(Buffer.from(base64EncodedImage, 'base64'), 'image/png')],
540
// {
541
// executionCount: 1,
542
// metadata: {
543
// needs_background: 'dark'
544
// },
545
// outputType: output_type
546
// }
547
// )
548
// ],
549
// propertiesToExcludeFromComparison
550
// );
551
// });
552
553
// test('png image with custom dimensions', async () => {
554
// validateCellOutputTranslation(
555
// [
556
// {
557
// execution_count: 1,
558
// data: {
559
// 'image/png': base64EncodedImage
560
// },
561
// metadata: {
562
// 'image/png': { height: '111px', width: '999px' }
563
// },
564
// output_type
565
// }
566
// ],
567
// [
568
// new vscode.NotebookCellOutput(
569
// [new vscode.NotebookCellOutputItem(Buffer.from(base64EncodedImage, 'base64'), 'image/png')],
570
// {
571
// executionCount: 1,
572
// metadata: {
573
// 'image/png': { height: '111px', width: '999px' }
574
// },
575
// outputType: output_type
576
// }
577
// )
578
// ],
579
// propertiesToExcludeFromComparison
580
// );
581
// });
582
583
// test('png allowed to scroll', async () => {
584
// validateCellOutputTranslation(
585
// [
586
// {
587
// execution_count: 1,
588
// data: {
589
// 'image/png': base64EncodedImage
590
// },
591
// metadata: {
592
// unconfined: true,
593
// 'image/png': { width: '999px' }
594
// },
595
// output_type
596
// }
597
// ],
598
// [
599
// new vscode.NotebookCellOutput(
600
// [new vscode.NotebookCellOutputItem(Buffer.from(base64EncodedImage, 'base64'), 'image/png')],
601
// {
602
// executionCount: 1,
603
// metadata: {
604
// unconfined: true,
605
// 'image/png': { width: '999px' }
606
// },
607
// outputType: output_type
608
// }
609
// )
610
// ],
611
// propertiesToExcludeFromComparison
612
// );
613
// });
614
// });
615
// });
616
// });
617
618
// suite('Output Order', () => {
619
// test('Verify order of outputs', async () => {
620
// const dataAndExpectedOrder: { output: nbformat.IDisplayData; expectedMimeTypesOrder: string[] }[] = [
621
// {
622
// output: {
623
// data: {
624
// 'application/vnd.vegalite.v4+json': 'some json',
625
// 'text/html': '<a>Hello</a>'
626
// },
627
// metadata: {},
628
// output_type: 'display_data'
629
// },
630
// expectedMimeTypesOrder: ['application/vnd.vegalite.v4+json', 'text/html']
631
// },
632
// {
633
// output: {
634
// data: {
635
// 'application/vnd.vegalite.v4+json': 'some json',
636
// 'application/javascript': 'some js',
637
// 'text/plain': 'some text',
638
// 'text/html': '<a>Hello</a>'
639
// },
640
// metadata: {},
641
// output_type: 'display_data'
642
// },
643
// expectedMimeTypesOrder: [
644
// 'application/vnd.vegalite.v4+json',
645
// 'text/html',
646
// 'application/javascript',
647
// 'text/plain'
648
// ]
649
// },
650
// {
651
// output: {
652
// data: {
653
// 'application/vnd.vegalite.v4+json': '', // Empty, should give preference to other mimetypes.
654
// 'application/javascript': 'some js',
655
// 'text/plain': 'some text',
656
// 'text/html': '<a>Hello</a>'
657
// },
658
// metadata: {},
659
// output_type: 'display_data'
660
// },
661
// expectedMimeTypesOrder: [
662
// 'text/html',
663
// 'application/javascript',
664
// 'text/plain',
665
// 'application/vnd.vegalite.v4+json'
666
// ]
667
// },
668
// {
669
// output: {
670
// data: {
671
// 'text/plain': 'some text',
672
// 'text/html': '<a>Hello</a>'
673
// },
674
// metadata: {},
675
// output_type: 'display_data'
676
// },
677
// expectedMimeTypesOrder: ['text/html', 'text/plain']
678
// },
679
// {
680
// output: {
681
// data: {
682
// 'application/javascript': 'some js',
683
// 'text/plain': 'some text'
684
// },
685
// metadata: {},
686
// output_type: 'display_data'
687
// },
688
// expectedMimeTypesOrder: ['application/javascript', 'text/plain']
689
// },
690
// {
691
// output: {
692
// data: {
693
// 'image/svg+xml': 'some svg',
694
// 'text/plain': 'some text'
695
// },
696
// metadata: {},
697
// output_type: 'display_data'
698
// },
699
// expectedMimeTypesOrder: ['image/svg+xml', 'text/plain']
700
// },
701
// {
702
// output: {
703
// data: {
704
// 'text/latex': 'some latex',
705
// 'text/plain': 'some text'
706
// },
707
// metadata: {},
708
// output_type: 'display_data'
709
// },
710
// expectedMimeTypesOrder: ['text/latex', 'text/plain']
711
// },
712
// {
713
// output: {
714
// data: {
715
// 'application/vnd.jupyter.widget-view+json': 'some widget',
716
// 'text/plain': 'some text'
717
// },
718
// metadata: {},
719
// output_type: 'display_data'
720
// },
721
// expectedMimeTypesOrder: ['application/vnd.jupyter.widget-view+json', 'text/plain']
722
// },
723
// {
724
// output: {
725
// data: {
726
// 'text/plain': 'some text',
727
// 'image/svg+xml': 'some svg',
728
// 'image/png': 'some png'
729
// },
730
// metadata: {},
731
// output_type: 'display_data'
732
// },
733
// expectedMimeTypesOrder: ['image/png', 'image/svg+xml', 'text/plain']
734
// }
735
// ];
736
737
// dataAndExpectedOrder.forEach(({ output, expectedMimeTypesOrder }) => {
738
// const sortedOutputs = jupyterCellOutputToCellOutput(output);
739
// const mimeTypes = sortedOutputs.items.map((item) => item.mime).join(',');
740
// assert.equal(mimeTypes, expectedMimeTypesOrder.join(','));
741
// });
742
// });
743
// });
744
});
745
746
function raceTimeout<T>(promise: Thenable<T>, timeout: number, onTimeout?: () => void): Promise<T | undefined> {
747
let promiseResolve: ((value: T | undefined) => void) | undefined = undefined;
748
749
const timer = setTimeout(() => {
750
promiseResolve?.(undefined);
751
onTimeout?.();
752
}, timeout);
753
754
return Promise.race([
755
Promise.resolve(promise).then(
756
result => {
757
clearTimeout(timer);
758
return result;
759
},
760
err => {
761
clearTimeout(timer);
762
throw err;
763
}
764
),
765
new Promise<T | undefined>(resolve => promiseResolve = resolve)
766
]);
767
}
768
769