Path: blob/main/extensions/copilot/test/simulation/notebooks.stest.ts
13388 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import assert from 'assert';6import * as fs from 'fs';7import { EOL } from 'os';8import * as path from 'path';9import type { NotebookDocument } from 'vscode';10import { Intent } from '../../src/extension/common/constants';11import { IDiffService } from '../../src/platform/diff/common/diffService';12import { DiffServiceImpl } from '../../src/platform/diff/node/diffServiceImpl';13import { IAlternativeNotebookContentService } from '../../src/platform/notebook/common/alternativeContent';14import { AlternativeNotebookContentEditGenerator, IAlternativeNotebookContentEditGenerator } from '../../src/platform/notebook/common/alternativeContentEditGenerator';15import { AlternativeNotebookFormat } from '../../src/platform/notebook/common/alternativeContentFormat';16import { MockAlternativeNotebookContentService } from '../../src/platform/notebook/common/mockAlternativeContentService';17import { INotebookService, VariablesResult } from '../../src/platform/notebook/common/notebookService';18import { ITestingServicesAccessor } from '../../src/platform/test/node/services';19import { IFile, SimulationWorkspace } from '../../src/platform/test/node/simulationWorkspace';20import { SimulationAlternativeNotebookContentService, SimulationNotebookService } from '../../src/platform/test/node/simulationWorkspaceServices';21import { ExtHostNotebookDocumentData } from '../../src/util/common/test/shims/notebookDocument';22import { DisposableStore } from '../../src/util/vs/base/common/lifecycle';23import { ResourceMap } from '../../src/util/vs/base/common/map';24import { Schemas } from '../../src/util/vs/base/common/network';25import { URI } from '../../src/util/vs/base/common/uri';26import { SyncDescriptor } from '../../src/util/vs/platform/instantiation/common/descriptors';27import { ssuite, stest } from '../base/stest';28import { getDiagnostics } from './diagnosticProviders';29import { DiagnosticsProvider, ITestDiagnostic } from './diagnosticProviders/diagnosticsProvider';30import { canExecutePythonCodeWithoutErrors, isValidPythonFile } from './diagnosticProviders/python';31import { simulateInlineChat } from './inlineChatSimulator';32import { fromFixture, getFixturesDir } from './stestUtil';33import { DiagnosticProviderId, IOutcome, IScenario } from './types';3435export function fromNotebookFixture(pathOrDirnameWithinFixturesDir: string, activeCell?: number /** when provided, code in other cells will be emptied */) {36const filePath: string = path.join(getFixturesDir(), pathOrDirnameWithinFixturesDir);37const baseDirname: string = path.dirname(filePath);38const fileName = path.relative(baseDirname, filePath);39const fileContents = fs.readFileSync(filePath).toString();4041try {42const notebook = JSON.parse(fileContents);43const cells = notebook.cells as any[];44notebook.cells = cells.map((cell, index) => {45if (index !== activeCell) {46return {47...cell,48source: ['']49};50} else {51return cell;52}53});5455return { kind: 'relativeFile' as const, fileName, fileContents: JSON.stringify(notebook, undefined, 2) };56} catch {57return { kind: 'relativeFile' as const, fileName, fileContents };58}59}6061ssuite({ title: 'notebook', subtitle: 'edit', location: 'inline' }, () => {6263stest({ description: 'variables', language: 'python' }, (testingServiceCollection) => {64return simulateInlineChat(testingServiceCollection, {65files: [fromFixture('notebook/variables.ipynb')],66queries: [67{68file: 'variables.ipynb',69activeCell: 0,70selection: [2, 0, 2, 0],71query: 'print seconds in a week',72expectedIntent: 'edit',73validate: async (outcome, workspace, accessor) => {74assert.strictEqual(outcome.type, 'inlineEdit');75assert.ok(/print\(seconds_in_a_week/.test(outcome.fileContents));76}77}78]79});80});8182stest({ description: 'dataframe', language: 'python' }, (testingServiceCollection) => {83return simulateInlineChat(testingServiceCollection, {84files: [fromFixture('notebook/dataframe.ipynb')],85queries: [86{87file: 'dataframe.ipynb',88activeCell: 2,89selection: [0, 0, 0, 0],90query: 'add a new column called adjusted to the dataframe and set it to the value of the activity column minus 2',91expectedIntent: 'edit',92validate: async (outcome, workspace, accessor) => {93assert.strictEqual(outcome.type, 'inlineEdit');94assert.ok(outcome.fileContents.includes('my_dataframe[\'adjusted\']') || outcome.fileContents.includes('my_dataframe[\"adjusted\"]'));95assert.ok(!outcome.fileContents.includes('import'));96}97}98]99});100});101102stest({ description: 'data cleansing', language: 'python' }, (testingServiceCollection) => {103return simulateInlineChat(testingServiceCollection, {104files: [fromFixture('notebook/datacleansing.ipynb')],105queries: [106{107file: 'datacleansing.ipynb',108activeCell: 2,109selection: [0, 0, 0, 0],110query: 'check for missing values',111expectedIntent: 'edit',112validate: async (outcome, workspace, accessor) => {113assert.strictEqual(outcome.type, 'inlineEdit');114assert.ok(!outcome.fileContents.includes('import'));115assert.ok(outcome.fileContents.includes('mydf'));116assert.ok(outcome.fileContents.includes('isnull') || outcome.fileContents.includes('dropna'));117}118}119]120});121});122123stest({ description: 'plot', language: 'python' }, (testingServiceCollection) => {124return simulateInlineChat(testingServiceCollection, {125files: [fromFixture('notebook/plot.ipynb')],126queries: [127{128file: 'plot.ipynb',129activeCell: 1,130selection: [0, 0, 0, 0],131query: 'plot the data frame',132expectedIntent: 'edit',133validate: async (outcome, workspace, accessor) => {134assert.strictEqual(outcome.type, 'inlineEdit');135assert.ok(!outcome.fileContents.includes('import'));136assert.ok(outcome.fileContents.includes('df.plot') || outcome.fileContents.includes('px.bar'));137}138}139]140});141});142143stest({ description: '/fix notebook exection ImportError', language: 'python' }, (testingServiceCollection) => {144return simulateInlineChat(testingServiceCollection, {145files: [fromFixture('notebook/errors.ipynb')],146queries: [147{148file: 'errors.ipynb',149activeCell: 0,150selection: [0, 0, 0, 0],151query: '/fix ModuleNotFoundError: No module named \'pandas\'',152expectedIntent: 'edit',153validate: async (outcome, workspace, accessor) => {154assert.strictEqual(outcome.type, 'inlineEdit');155assert.ok(!outcome.fileContents.includes('!pip'));156assert.ok(outcome.fileContents.includes('%pip install'));157assert.ok(outcome.fileContents.indexOf('pip') === outcome.fileContents.lastIndexOf('pip'));158}159}160]161});162});163164stest({ description: 'edit notebook code should not duplicate the content', language: 'python' }, (testingServiceCollection) => {165return simulateInlineChat(testingServiceCollection, {166files: [fromFixture('notebook/edit.ipynb')],167queries: [168{169file: 'edit.ipynb',170activeCell: 0,171selection: [6, 0, 8, 0],172query: 'make the plot larger',173expectedIntent: 'edit',174validate: async (outcome, workspace, accessor) => {175assert.strictEqual(outcome.type, 'inlineEdit');176assert.ok(177outcome.fileContents.includes('plt.figure')178|| outcome.fileContents.includes('plt.gcf')179);180// check if 'plt.figure' only shows up once181const matches = outcome.fileContents.match(/(plt\.figure)|(plt\.gcf)/g);182assert.strictEqual(matches?.length, 1);183}184}185]186});187});188189stest({ description: 'set index', language: 'python' }, (testingServiceCollection) => {190return simulateInlineChat(testingServiceCollection, {191files: [fromFixture('notebook/edit.ipynb')],192queries: [193{194file: 'edit.ipynb',195activeCell: 1,196selection: [13, 0, 13, 0],197query: 'Set the \'origin\' colum as the index of the dataframe',198expectedIntent: 'edit',199validate: async (outcome, workspace, accessor) => {200assert.strictEqual(outcome.type, 'inlineEdit');201assert.ok(outcome.fileContents.includes('.set_index'));202assert.strictEqual(outcome.fileContents.match(/set\_index/g)?.length, 1);203assert.strictEqual(outcome.fileContents.match(/DataFrame/g)?.length, 1);204}205}206]207});208});209210stest({ description: 'group by', language: 'python' }, (testingServiceCollection) => {211return simulateInlineChat(testingServiceCollection, {212files: [fromFixture('notebook/edit.ipynb')],213queries: [214{215file: 'edit.ipynb',216activeCell: 2,217selection: [6, 0, 6, 0],218query: 'Group the entire dataframe by regiment and company',219expectedIntent: 'edit',220validate: async (outcome, workspace, accessor) => {221assert.strictEqual(outcome.type, 'inlineEdit');222assert.ok(outcome.fileContents.includes('regiment.groupby'));223assert.strictEqual(outcome.fileContents.match(/groupby/g)?.length, 1);224assert.strictEqual(outcome.fileContents.match(/DataFrame/g)?.length, 1);225}226}227]228});229});230231// import matplotlib.pyplot as plt232233// months = range(1, 13)234// nyc_temp_2000 = [20.0, 30.5, 80.1, 80.3, 56.5, 99.6]235// plt.plot(months, nyc_temp_2000)236// stest({ description: '/fix Matplotlib: x and y must have same first dimension', language: 'python' }, (testingServiceCollection) => {237// return runScenario(accessor, {238// files: [fromFixture('notebook/errors.ipynb')],239// queries: [240// {241// file: 'errors.ipynb',242// activeCell: 6,243// selection: [4, 0, 4, 0],244// query: '/fix ValueError: x and y must have same first dimension, but have shapes (12,) and (6,)',245// expectedIntent: 'edit',246// validate: async (outcome, workspace, accessor) => {247// assert.strictEqual(outcome.type, 'inlineEdit');248// assert.strictEqual(outcome.appliedEdits.length, 1);249// const edit = outcome.appliedEdits[0];250// assert.ok(edit.newText.includes('global'));251// }252// }253// ]254// });255// });256257258259260// NameError: name 'df' is not defined -> should suggest rerun cell261});262263ssuite({ title: 'notebook', subtitle: 'generate', location: 'inline' }, () => {264265stest({ description: 'edit markdown cell should support code example', language: 'markdown' }, (testingServiceCollection) => {266return simulateInlineChat(testingServiceCollection, {267files: [fromFixture('notebook/md.ipynb')],268queries: [269{270file: 'md.ipynb',271activeCell: 0,272selection: [0, 0, 0, 0],273query: 'describe fibonacci algorithm in markdown, along with code example',274expectedIntent: 'generate',275validate: async (outcome, workspace, accessor) => {276assert.strictEqual(outcome.type, 'inlineEdit');277assert.ok(outcome.fileContents.includes('```'));278const matches = outcome.fileContents.match(/\`\`\`/g);279assert.ok(matches && matches.length > 0 && matches.length % 2 === 0);280}281}282]283});284});285286stest({ description: 'Which was the most-ordered item', language: 'python' }, (testingServiceCollection) => {287return simulateInlineChat(testingServiceCollection, {288files: [fromFixture('notebook/sales.ipynb')],289queries: [290{291file: 'sales.ipynb',292activeCell: 13,293selection: [0, 0, 0, 0],294query: 'Which was the most-ordered item? ', // How many items were orderd in total?295expectedIntent: 'generate',296validate: async (outcome, workspace, accessor) => {297assert.strictEqual(outcome.type, 'inlineEdit');298}299}300]301});302});303304stest({ description: 'How many items were orderd in total?', language: 'python' }, (testingServiceCollection) => {305return simulateInlineChat(testingServiceCollection, {306files: [fromFixture('notebook/sales.ipynb')],307queries: [308{309file: 'sales.ipynb',310activeCell: 13,311selection: [0, 0, 0, 0],312query: 'WHow many items were orderd in total?',313expectedIntent: 'generate',314validate: async (outcome, workspace, accessor) => {315assert.strictEqual(outcome.type, 'inlineEdit');316}317}318]319});320});321322stest({ description: 'create a model to predict the likelihood of a flight being delayed', language: 'python' }, (testingServiceCollection) => {323return simulateInlineChat(testingServiceCollection, {324files: [fromFixture('notebook/model.ipynb')],325queries: [326{327file: 'model.ipynb',328activeCell: 1,329selection: [0, 0, 0, 0],330query: 'create a model to predict the likelihood of a flight being delayed based on the day of the week and the arrival airport. Use Logistic regression and calculate the accuracy of the model.', //331expectedIntent: 'generate',332validate: async (outcome, workspace, accessor) => {333assert.strictEqual(outcome.type, 'inlineEdit');334}335}336]337});338});339});340341ssuite({ title: 'notebook', subtitle: 'generate runtime', location: 'inline' }, () => {342stest({ description: 'generate code uses obselete variable', language: 'python' }, (testingServiceCollection) => {343const file = fromFixture('notebook/variablesruntime.ipynb');344const testScenario: IScenario = {345files: [file],346queries: [347{348file: 'variablesruntime.ipynb',349activeCell: 1,350selection: [2, 0, 2, 0],351query: 'Detect and remove outliers for delay columns',352expectedIntent: 'edit',353validate: async (outcome, workspace, accessor) => {354assert.strictEqual(outcome.type, 'inlineEdit');355assert.strictEqual(outcome.fileContents.indexOf('[delay_columns]') >= 0 || outcome.fileContents.indexOf('delay_columns =') >= 0 || outcome.fileContents.indexOf('delay_columns:') >= 0, true);356}357}358],359extraWorkspaceSetup: async (workspace) => {360const notebook = workspace.getNotebookDocuments()[0];361if (notebook) {362const variables: VariablesResult[] = [363{364variable: {365name: 'delay_columns',366value: `['DepDelay', 'ArrDelay']`,367type: 'list'368},369hasNamedChildren: false,370indexedChildrenCount: 2371}372];373testingServiceCollection.define(INotebookService, new SyncDescriptor(374SimulationNotebookService,375[376workspace,377new ResourceMap<VariablesResult[]>([378[379notebook.uri,380variables381]382])383]384));385testingServiceCollection.define(IAlternativeNotebookContentService, new SyncDescriptor(386SimulationAlternativeNotebookContentService,387[]388));389testingServiceCollection.define(IAlternativeNotebookContentEditGenerator, new SyncDescriptor(390AlternativeNotebookContentEditGenerator391));392testingServiceCollection.define(IDiffService, new SyncDescriptor(393DiffServiceImpl394));395}396}397};398return simulateInlineChat(testingServiceCollection, testScenario);399});400});401402ssuite({ title: 'notebook', subtitle: 'fix runtime', location: 'inline' }, () => {403stest({ description: '/fix notebook execution ImportError, insert at top', language: 'python' }, (testingServiceCollection) => {404return simulateInlineChat(testingServiceCollection, {405files: [fromFixture('notebook/errors.ipynb')],406queries: [407{408file: 'errors.ipynb',409activeCell: 0,410selection: [0, 0, 0, 0],411query: '/fix ModuleNotFoundError: No module named \'pandas\'',412expectedIntent: 'edit',413validate: async (outcome, workspace, accessor) => {414assert.strictEqual(outcome.type, 'inlineEdit');415// assert(outcome.appliedEdits.length > 0, 'at least 1 edit generated');416assert.ok(outcome.fileContents.indexOf('import pandas') >= 0);417// assert.ok(await isValidPythonFile(accessor, outcome.fileContents));418}419}420]421});422});423424stest({ description: '/fix ValueError: The truth value of an array with more than one element is ambiguous', language: 'python' }, (testingServiceCollection) => {425return simulateInlineChat(testingServiceCollection, {426files: [fromFixture('notebook/errors.ipynb')],427queries: [428{429file: 'errors.ipynb',430activeCell: 1,431selection: [4, 0, 4, 0],432query: '/fix ValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()',433expectedIntent: 'edit',434validate: async (outcome, workspace, accessor) => {435assert.strictEqual(outcome.type, 'inlineEdit');436if (!outcome.fileContents.includes('A | B') && !outcome.fileContents.includes('np.logical_or(A, B)')) {437assert.ok(outcome.fileContents.includes('A.any()'));438assert.ok(outcome.fileContents.includes('B.any()'));439}440}441}442]443});444});445446stest({ description: '/fix Tensorflow InvalidArgumentError', language: 'python' }, (testingServiceCollection) => {447return simulateInlineChat(testingServiceCollection, {448files: [fromFixture('notebook/errors.ipynb')],449queries: [450{451file: 'errors.ipynb',452activeCell: 2,453selection: [1, 0, 1, 0],454query: '/fix InvalidArgumentError: {{function_node __wrapped__Reshape_device_/job:localhost/replica:0/task:0/device:CPU:0}} Input to reshape is a tensor with 3 values, but the requested shape has 2 [Op:Reshape]',455expectedIntent: 'edit',456validate: async (outcome, workspace, accessor) => {457assert.strictEqual(outcome.type, 'inlineEdit');458assert.ok(outcome.fileContents.includes('reshape'));459}460}461]462});463});464465stest({ description: '/fix Tensorflow model has not yet been built', language: 'python' }, (testingServiceCollection) => {466return simulateInlineChat(testingServiceCollection, {467files: [fromNotebookFixture('notebook/errors.ipynb', 3)],468queries: [469{470file: 'errors.ipynb',471activeCell: 3,472selection: [4, 0, 4, 0],473query: '/fix ValueError: This model has not yet been built. Build the model first by calling `build()` or by calling the model on a batch of data.',474expectedIntent: 'edit',475validate: async (outcome, workspace, accessor) => {476assert.strictEqual(outcome.type, 'inlineEdit');477// assert.ok(outcome.fileContents.includes('.build'));478}479}480]481});482});483484stest({ description: '/fix numpy, unsupported operand types', language: 'python' }, (testingServiceCollection) => {485return simulateInlineChat(testingServiceCollection, {486files: [fromNotebookFixture('notebook/errors.ipynb', 4)],487queries: [488{489file: 'errors.ipynb',490activeCell: 4,491selection: [3, 0, 3, 0],492query: '/fix TypeError: unsupported operand type(s) for +: \'int\' and \'NoneType\'',493expectedIntent: 'edit',494validate: async (outcome, workspace, accessor) => {495assert.strictEqual(outcome.type, 'inlineEdit');496assert.ok(497!outcome.fileContents.includes('None') ||498outcome.fileContents.includes(' != None') ||499outcome.fileContents.includes(' == None') || (500(outcome.fileContents.includes('np.array([1, np.nan, 3, 4])') && outcome.fileContents.includes('nansum(vals1)'))501));502}503}504]505});506});507508stest({ description: '/fix UnboundLocalError, local variable referenced before assignment', language: 'python' }, (testingServiceCollection) => {509return simulateInlineChat(testingServiceCollection, {510files: [fromFixture('notebook/errors.ipynb')],511queries: [512{513file: 'errors.ipynb',514activeCell: 5,515selection: [3, 0, 3, 0],516query: '/fix UnboundLocalError: local variable \'a_var\' referenced before assignment',517expectedIntent: 'edit',518validate: async (outcome, workspace, accessor) => {519assert.strictEqual(outcome.type, 'inlineEdit');520}521}522]523});524});525526stest({ description: '/fix name conflict with builtin function', language: 'python' }, (testingServiceCollection) => {527return simulateInlineChat(testingServiceCollection, {528files: [fromFixture('notebook/errors.ipynb')],529queries: [530{531file: 'errors.ipynb',532activeCell: 6,533selection: [0, 0, 4, 16],534query: '/fix TypeError: \'int\' object is not callable',535expectedIntent: 'edit',536validate: async (outcome, workspace, accessor) => {537assert.strictEqual(outcome.type, 'inlineEdit');538assert.ok(!outcome.fileContents.includes('max = 0'));539}540}541]542});543});544545stest({ description: '/fix AttributeError: can\'t set attribute', language: 'python' }, (testingServiceCollection) => {546return simulateInlineChat(testingServiceCollection, {547files: [fromFixture('notebook/errors.ipynb')],548queries: [549{550file: 'errors.ipynb',551activeCell: 7,552selection: [9, 0, 9, 0],553query: '/fix AttributeError: can\'t set attribute',554expectedIntent: 'edit',555validate: async (outcome, workspace, accessor) => {556assert.strictEqual(outcome.type, 'inlineEdit');557assert.ok(outcome.fileContents.includes('@x.setter'));558}559}560]561});562});563564stest({ description: '/fix TypeError: Index does not support mutable operations', language: 'python' }, (testingServiceCollection) => {565return simulateInlineChat(testingServiceCollection, {566files: [fromFixture('notebook/errors.ipynb')],567queries: [568{569file: 'errors.ipynb',570activeCell: 8,571selection: [3, 0, 3, 0],572query: '/fix TypeError: Index does not support mutable operations',573expectedIntent: 'edit',574validate: async (outcome, workspace, accessor) => {575assert.strictEqual(outcome.type, 'inlineEdit');576assert.ok(outcome.fileContents.includes('ind.set_value') || outcome.fileContents.includes('list(ind)') || outcome.fileContents.includes('ind.tolist()') || outcome.fileContents.includes('ind.delete('));577}578}579]580});581});582583stest({ description: '/fix TypeError: str object is not an iterator', language: 'python' }, (testingServiceCollection) => {584return simulateInlineChat(testingServiceCollection, {585files: [fromFixture('notebook/errors.ipynb')],586queries: [587{588file: 'errors.ipynb',589activeCell: 9,590selection: [1, 0, 1, 0],591query: '/fix TypeError: str object is not an iterator',592expectedIntent: 'edit',593validate: async (outcome, workspace, accessor) => {594assert.strictEqual(outcome.type, 'inlineEdit');595assert.ok(outcome.fileContents.includes('iter('));596}597}598]599});600});601602stest({ description: '/fix TypeError: can only concatenate str (not "int") to str', language: 'python' }, (testingServiceCollection) => {603return simulateInlineChat(testingServiceCollection, {604files: [fromNotebookFixture('notebook/errors.ipynb', 10)],605queries: [606{607file: 'errors.ipynb',608activeCell: 10,609selection: [0, 0, 0, 0],610query: '/fix TypeError: can only concatenate str (not "int") to str',611expectedIntent: 'edit',612validate: async (outcome, workspace, accessor) => {613assert.strictEqual(outcome.type, 'inlineEdit');614assert.ok(outcome.fileContents.includes('float(') || outcome.fileContents.includes('int(') || outcome.fileContents.includes('str('));615}616}617]618});619});620621stest({ description: '/fix Missing import, name \'array\' is not defined', language: 'python' }, (testingServiceCollection) => {622return simulateInlineChat(testingServiceCollection, {623files: [fromFixture('notebook/errors.ipynb')],624queries: [625{626file: 'errors.ipynb',627activeCell: 11,628selection: [0, 0, 0, 0],629query: '/fix NameError: name \'array\' is not defined',630expectedIntent: 'edit',631validate: async (outcome, workspace, accessor) => {632assert.strictEqual(outcome.type, 'inlineEdit');633assert.ok(outcome.fileContents.includes('np.array') || outcome.fileContents.includes('from numpy import'));634// assert.ok(await isValidPythonFile(accessor, outcome.fileContents));635}636}637]638});639});640641stest({ description: '/fix can only concatenate list (not "str") to list', language: 'python' }, (testingServiceCollection) => {642return simulateInlineChat(testingServiceCollection, {643files: [fromFixture('notebook/errors.ipynb')],644queries: [645{646file: 'errors.ipynb',647activeCell: 12,648selection: [1, 0, 1, 0],649query: '/fix TypeError: can only concatenate list (not "str") to list',650expectedIntent: 'edit',651validate: async (outcome, workspace, accessor) => {652assert.strictEqual(outcome.type, 'inlineEdit');653assert.ok(outcome.fileContents.includes('[\'bar\']') || outcome.fileContents.includes('foo.append'));654assert.ok(await isValidPythonFile(accessor, outcome.fileContents));655}656}657]658});659});660});661662ssuite({ title: 'notebook', subtitle: 'fix', location: 'inline' }, () => {663stest({ description: 'cannot instantiate abstract class', language: 'python' }, (testingServiceCollection) => {664return simulateInlineChat(testingServiceCollection, {665files: [fromFixture('notebook/fixing/fixing0.ipynb')],666queries: [667{668file: 'fixing0.ipynb',669selection: [9, 4, 9, 4],670activeCell: 0,671query: `/fix Cannot instantiate abstract class "Base"\n "Base.foo" is abstract`,672expectedIntent: Intent.Fix,673diagnostics: 'pyright',674validate: async (outcome, workspace, accessor) => assertNoCellDiagnosticsAsync(accessor, outcome, workspace, 'pyright')675}676]677});678});679680stest({ description: 'all Annotated types should include at least two type arguments', language: 'python' }, (testingServiceCollection) => {681return simulateInlineChat(testingServiceCollection, {682files: [fromFixture('notebook/fixing/fixing1.ipynb')],683queries: [684{685file: 'fixing1.ipynb',686selection: [4, 3, 4, 3],687activeCell: 0,688query: `/fix Expected one type argument and one or more annotations for "Annotated"`,689expectedIntent: Intent.Fix,690diagnostics: 'pyright',691validate: async (outcome, workspace, accessor) => assertNoCellDiagnosticsAsync(accessor, outcome, workspace, 'pyright')692}693]694});695});696697stest({ description: 'should not generate an error for variables declared in outer scopes', language: 'python' }, (testingServiceCollection) => {698return simulateInlineChat(testingServiceCollection, {699files: [fromFixture('notebook/fixing/fixing2.ipynb')],700queries: [701{702file: 'fixing2.ipynb',703selection: [24, 8, 24, 8],704activeCell: 0,705query: `/fix "d" is not defined`,706expectedIntent: Intent.Fix,707diagnostics: 'pyright',708validate: async (outcome, workspace, accessor) => assertNoCellDiagnosticsAsync(accessor, outcome, workspace, 'pyright')709}710]711});712});713714stest({ description: 'async cannot be used in a non-async function', language: 'python' }, (testingServiceCollection) => {715return simulateInlineChat(testingServiceCollection, {716files: [fromFixture('notebook/fixing/fixing3.ipynb')],717queries: [718{719file: 'fixing3.ipynb',720selection: [17, 4, 17, 4],721activeCell: 0,722query: `/fix Use of "async" not allowed outside of async function`,723expectedIntent: Intent.Fix,724diagnostics: 'pyright',725validate: async (outcome, workspace, accessor) => assertNoCellDiagnosticsAsync(accessor, outcome, workspace, 'pyright')726}727]728});729});730731stest({ description: 'await cannot be used in a non-async function', language: 'python' }, (testingServiceCollection) => {732return simulateInlineChat(testingServiceCollection, {733files: [fromFixture('notebook/fixing/fixing4.ipynb')],734queries: [735{736file: 'fixing4.ipynb',737selection: [14, 0, 14, 0],738activeCell: 0,739query: `/fix "await" allowed only within async function`,740expectedIntent: Intent.Fix,741diagnostics: 'pyright',742validate: async (outcome, workspace, accessor) => assertNoCellDiagnosticsAsync(accessor, outcome, workspace, 'pyright')743}744]745});746});747748stest({ description: 'bad token', language: 'python' }, (testingServiceCollection) => {749return simulateInlineChat(testingServiceCollection, {750files: [fromFixture('notebook/fixing/fixing5.ipynb')],751queries: [752{753file: 'fixing5.ipynb',754selection: [4, 7, 4, 7],755activeCell: 0,756query: `/fix Invalid character in identifier`,757expectedIntent: Intent.Fix,758diagnostics: 'pyright',759validate: async (outcome, workspace, accessor) => assertNoCellDiagnosticsAsync(accessor, outcome, workspace, 'pyright')760}761]762});763});764765stest({ description: 'Bar does not define a do_something2 method', language: 'python' }, (testingServiceCollection) => {766return simulateInlineChat(testingServiceCollection, {767files: [fromFixture('notebook/fixing/fixing6.ipynb')],768queries: [769{770file: 'fixing6.ipynb',771selection: [28, 0, 28, 0],772activeCell: 0,773query: [774`/fix Cannot access member "do_something2" for type "Bar"`,775` Member "do_something2" is unknown`776].join('\n'),777expectedIntent: Intent.Fix,778diagnostics: 'pyright',779validate: async (outcome, workspace, accessor) => assertNoCellDiagnosticsAsync(accessor, outcome, workspace, 'pyright')780}781]782});783});784785// Inspired by case 35 of /fix dataset version 10786// In the AML run, copilot did not understand the error and did not fix it787stest({ description: '(AML-10-35) can not access member', language: 'python' }, (testingServiceCollection) => {788return simulateInlineChat(testingServiceCollection, {789files: [fromFixture('notebook/fixing/fixing7.ipynb')],790queries: [791{792file: 'fixing7.ipynb',793selection: [2, 23, 2, 23],794activeCell: 0,795query: [796`/fix Cannot access member "includes" for type "set[Unknown]"`,797` Member "includes" is unknown`798].join('\n'),799expectedIntent: Intent.Fix,800diagnostics: 'pyright',801validate: async (outcome, workspace, accessor) => assertNoCellDiagnosticsAsync(accessor, outcome, workspace, 'pyright')802}803]804});805});806807// Inspired by case 36 of /fix dataset version 10808// In the AML run, copilot did not understand the error and did not fix it809stest({ description: '(AML-10-36) can not be assigned 2', language: 'python' }, (testingServiceCollection) => {810return simulateInlineChat(testingServiceCollection, {811files: [fromFixture('notebook/fixing/fixing8.ipynb')],812queries: [813{814file: 'fixing8.ipynb',815selection: [4, 19, 4, 19],816activeCell: 0,817query: `/fix Expression of type "list[None]" cannot be assigned to declared type "List[int] | None"`,818expectedIntent: Intent.Fix,819diagnostics: 'pyright',820validate: async (outcome, workspace, accessor) => assertNoCellDiagnosticsAsync(accessor, outcome, workspace, 'pyright')821}822]823});824});825826// Inspired by case 4 of /fix dataset version 10827// In the AML run, copilot did not understand the error and did not fix it828stest({ description: '(AML-10-4) parameter already assigned', language: 'python' }, (testingServiceCollection) => {829return simulateInlineChat(testingServiceCollection, {830files: [fromFixture('notebook/fixing/fixing9.ipynb')],831queries: [832{833file: 'fixing9.ipynb',834selection: [7, 33, 7, 33],835activeCell: 0,836query: `/fix Parameter "input_shape" is already assigned`,837expectedIntent: Intent.Fix,838diagnostics: 'pyright',839validate: async (outcome, workspace, accessor) => assertNoCellDiagnosticsAsync(accessor, outcome, workspace, 'pyright')840}841]842});843});844845// Inspired by case 48 of /fix dataset version 10846// In the AML run, copilot did not understand the error and did not fix it847stest({ description: '(AML-10-48) can not be assigned 3', language: 'python' }, (testingServiceCollection) => {848return simulateInlineChat(testingServiceCollection, {849files: [fromFixture('notebook/fixing/fixing10.ipynb')],850queries: [851{852file: 'fixing10.ipynb',853selection: [9, 14, 9, 14],854activeCell: 0,855query: [856`/fix Argument of type "dict[str, int]" cannot be assigned to parameter "platforms" of type "list[str] | str" in function "setup"`,857` Type "dict[str, int]" cannot be assigned to type "list[str] | str"`,858` "dict[str, int]" is incompatible with "list[str]"`,859` "dict[str, int]" is incompatible with "str"`,860].join('\n'),861expectedIntent: Intent.Fix,862diagnostics: 'pyright',863validate: async (outcome, workspace, accessor) => assertNoCellDiagnosticsAsync(accessor, outcome, workspace, 'pyright')864}865]866});867});868869// Inspired by case 58 of /fix dataset version 10870// The AML run has removed a big part of the code and replaced with non-minimal edits, this initial issue was not resolved871stest({ description: '(AML-10-58) not defined', language: 'python' }, (testingServiceCollection) => {872return simulateInlineChat(testingServiceCollection, {873files: [fromFixture('notebook/fixing/fixing11.ipynb')],874queries: [875{876file: 'fixing11.ipynb',877selection: [7, 20, 7, 20],878activeCell: 0,879query: `/fix "T_Or" is not defined`,880expectedIntent: Intent.Fix,881diagnostics: 'pyright',882validate: async (outcome, workspace, accessor) => assertNoCellDiagnosticsAsync(accessor, outcome, workspace, 'pyright')883}884]885});886});887888// Inspired by case 29 of /fix dataset version 10889stest({ description: '(AML-10-29) instance of bool has no to_string member', language: 'python' }, (testingServiceCollection) => {890return simulateInlineChat(testingServiceCollection, {891files: [fromFixture('notebook/fixing/fixing12.ipynb')],892queries: [893{894file: 'fixing12.ipynb',895selection: [1, 19, 1, 19],896activeCell: 0,897query: `/fix`,898expectedIntent: Intent.Fix,899diagnostics: 'pyright',900validate: async (outcome, workspace, accessor) => assertNoCellDiagnosticsAsync(accessor, outcome, workspace, 'pyright')901}902]903});904});905906// Inspired by case 110 of /fix dataset version 8907stest({ description: '(AML-8-110) not defined', language: 'python' }, (testingServiceCollection) => {908return simulateInlineChat(testingServiceCollection, {909files: [fromFixture('notebook/fixing/fixing13.ipynb')],910queries: [911{912file: 'fixing13.ipynb',913selection: [7, 20, 7, 20],914activeCell: 0,915query: `/fix Instance methods should take a "self" parameter`,916expectedIntent: Intent.Fix,917diagnostics: 'pyright',918validate: async (outcome, workspace, accessor) => assertNoCellDiagnosticsAsync(accessor, outcome, workspace, 'pyright')919}920]921});922});923924925// Inspired by case 73 of /fix dataset version 8926stest({ description: '(AML-8-73) no value for argument in function call', language: 'python' }, (testingServiceCollection) => {927return simulateInlineChat(testingServiceCollection, {928files: [fromFixture('notebook/fixing/fixing14.ipynb')],929queries: [930{931file: 'fixing14.ipynb',932selection: [12, 16, 12, 16],933activeCell: 0,934query: `/fix Argument missing for parameter "error_message"`,935expectedIntent: Intent.Fix,936diagnostics: 'pyright',937validate: async (outcome, workspace, accessor) => assertNoCellDiagnosticsAsync(accessor, outcome, workspace, 'pyright')938}939]940});941});942943stest({ description: 'undefined variable', language: 'python' }, (testingServiceCollection) => {944return simulateInlineChat(testingServiceCollection, {945files: [fromFixture('notebook/fixing/fixing15.ipynb')],946queries: [947{948file: 'fixing15.ipynb',949selection: [0, 0, 0, 4],950activeCell: 0,951query: `/fix "Play" is not defined`,952expectedIntent: Intent.Fix,953diagnostics: 'pyright',954validate: async (outcome, workspace, accessor) => assertNoCellDiagnosticsAsync(accessor, outcome, workspace, 'pyright')955}956]957});958});959960stest({ description: 'general type issue', language: 'python' }, (testingServiceCollection) => {961return simulateInlineChat(testingServiceCollection, {962files: [fromFixture('notebook/fixing/fixing16.ipynb')],963queries: [964{965file: 'fixing16.ipynb',966selection: [29, 22, 29, 25],967activeCell: 0,968query: [969`/fix Argument of type "Msg[Foo]" cannot be assigned to parameter "msg" of type "Msg[FooBar]" in function "handle"`,970` "Msg[Foo]" is incompatible with "Msg[FooBar]"`,971` Type parameter "T@Msg" is invariant, but "Foo" is not the same as "FooBar"`972].join('\n'),973expectedIntent: Intent.Fix,974diagnostics: 'pyright',975validate: async (outcome, workspace, accessor) => assertNoCellDiagnosticsAsync(accessor, outcome, workspace, 'pyright')976}977]978});979});980981stest({ description: 'optional member access', language: 'python' }, (testingServiceCollection) => {982return simulateInlineChat(testingServiceCollection, {983files: [fromFixture('notebook/fixing/fixing17.ipynb')],984queries: [985{986file: 'fixing17.ipynb',987selection: [12, 23, 12, 28],988activeCell: 0,989query: `/fix "upper" is not a known member of "None"`,990expectedIntent: Intent.Fix,991diagnostics: 'pyright',992validate: async (outcome, workspace, accessor) => assertNoCellDiagnosticsAsync(accessor, outcome, workspace, 'pyright')993}994]995});996});997998stest({ description: 'unbound variable', language: 'python' }, (testingServiceCollection) => {999return simulateInlineChat(testingServiceCollection, {1000files: [fromFixture('notebook/fixing/fixing18.ipynb')],1001queries: [1002{1003file: 'fixing18.ipynb',1004selection: [4, 11, 4, 12],1005activeCell: 0,1006query: `/fix "a" is possibly unbound`,1007expectedIntent: Intent.Fix,1008diagnostics: 'pyright',1009validate: async (outcome, workspace, accessor) => assertNoCellDiagnosticsAsync(accessor, outcome, workspace, 'pyright')1010}1011]1012});1013});1014});10151016const shouldEvaluate = 1 >> 1;10171018ssuite.optional((opes) => !shouldEvaluate, { title: 'notebook', subtitle: 'mbpp', location: 'inline' }, () => {1019const mbppFile = fromFixture('notebook/filtered-mbpp.json');1020const mbppTests = JSON.parse(mbppFile.fileContents).tests;1021const testScenarios: { task_id: number; text: string; test_list: string[] }[] = mbppTests.map((test: any) => ({1022task_id: test.task_id,1023text: test.prompt,1024test_list: test.test_list.map((line: string) => `${line}\n`)1025}));10261027for (const test of testScenarios) {1028const prompt = test.text;1029const test_list = test.test_list;1030const task_id = test.task_id;10311032stest({ description: 'mbpp ' + task_id + ' ' + prompt, language: 'python' }, async (testingServiceCollection) => {1033const templateFile = fromFixture('notebook/mbpp.ipynb');10341035const notebook = JSON.parse(templateFile.fileContents);1036const cells = notebook.cells as any[];1037const cell = cells[1];1038cell.source = test_list;10391040const notebookFile = { kind: 'relativeFile' as const, fileName: templateFile.fileName, fileContents: JSON.stringify(notebook, undefined, 2) };10411042return simulateInlineChat(testingServiceCollection, {1043files: [notebookFile],1044queries: [1045{1046file: notebookFile.fileName,1047activeCell: 0,1048selection: [0, 0, 0, 0],1049query: `${prompt} Your code should pass the tests in the next cell.`,1050expectedIntent: 'edit',1051validate: async (outcome, _workspace, accessor) => {1052assert.strictEqual(outcome.type, 'inlineEdit');10531054if (shouldEvaluate) {1055const testTimeoutPromise = new Promise<void>((_, reject) => {1056setTimeout(() => reject(new Error('Test execution timed out')), 60000);1057});10581059await Promise.race([testTimeoutPromise, assertPythonCodeIsValid(accessor, outcome.fileContents, 'Generated Code is not valid', 'Generated not did not execute without errors')]);10601061const codeWithTests = `${outcome.fileContents}${EOL}${EOL}${EOL}# Tests${EOL}${EOL}${test_list.join('')}`;1062await Promise.race([testTimeoutPromise, assertPythonCodeIsValid(accessor, codeWithTests, 'Generated Code with Tests is not valid', 'Generated did not pass the tests')]);1063}1064}1065}1066]1067});1068});1069}1070});10711072function generateMBPPNotebookFixture(pathOrDirnameWithinFixturesDir: string, tests: string[]) {1073const filePath = path.join(getFixturesDir(), pathOrDirnameWithinFixturesDir);1074const baseDirname = path.dirname(filePath);1075const fileName = path.relative(baseDirname, filePath);1076const uri = URI.parse(filePath);1077const fileContents = fs.readFileSync(filePath).toString();10781079try {1080const notebook = JSON.parse(fileContents);1081const cells = notebook.cells as any[];1082const cell = cells[1];1083cell.source = tests;10841085return { kind: 'relativeFile' as const, uri, fileName, fileContents: JSON.stringify(notebook, undefined, 2) };1086} catch {1087return { kind: 'relativeFile' as const, uri, fileName, fileContents };1088}1089}109010911092ssuite.optional((opes) => !shouldEvaluate || true, { title: 'notebook', subtitle: 'notebookEditsMbpp', location: 'panel' }, () => {1093const mbppFile = fromFixture('notebook/filtered-mbpp.json');1094const mbppTests = JSON.parse(mbppFile.fileContents).tests;1095const testScenarios: { task_id: number; text: string; test_list: string[] }[] = mbppTests.map((test: any) => ({1096task_id: test.task_id,1097text: test.prompt,1098test_list: test.test_list.map((line: string) => `${line}\n`)1099}));11001101for (const test of testScenarios) {1102const prompt = test.text;1103const test_list = test.test_list;1104const task_id = test.task_id;11051106stest({ description: 'mbpp ' + task_id + ' ' + prompt, language: 'python' }, async (testingServiceCollection) => {1107const notebookFile = generateMBPPNotebookFixture('notebook/mbpp.ipynb', test_list);1108const disposables = new DisposableStore();11091110const nbJson = JSON.parse(notebookFile.fileContents);1111const cells = nbJson.cells as any[];1112const cell = cells[1];1113cell.source = test_list;11141115let notebook: NotebookDocument;1116const currentFile: IFile = {1117kind: 'qualifiedFile',1118uri: notebookFile.uri,1119fileContents: JSON.stringify(nbJson, undefined, 2)1120};11211122return simulateInlineChat(testingServiceCollection, {1123files: [currentFile],1124async extraWorkspaceSetup(workspace) {1125const extHostNotebook = ExtHostNotebookDocumentData.createJupyterNotebook(notebookFile.uri, notebookFile.fileContents, workspace);1126notebook = extHostNotebook.document;1127// TODO@DonJayamanne1128},1129async onBeforeStart(accessor) {1130(accessor.get<IAlternativeNotebookContentService>(IAlternativeNotebookContentService) as MockAlternativeNotebookContentService).format = AlternativeNotebookFormat.json;1131},1132queries: [1133{1134file: currentFile.uri,1135activeCell: 0,1136selection: [1, 0, 1, 0],1137query: `${prompt} Your code should pass the tests in the next cell.`,1138expectedIntent: 'edit',1139validate: async (_outcome, _workspace, accessor) => {1140if (shouldEvaluate) {1141const testTimeoutPromise = new Promise<void>((_, reject) => {1142setTimeout(() => reject(new Error('Test execution timed out')), 60000);1143});11441145await Promise.race([testTimeoutPromise, assertPythonCodeIsValid(accessor, notebook.cellAt(0).document.getText(), 'Generated Code is not valid', 'Generated not did not execute without errors')]);11461147const codeWithTests = `${notebook.cellAt(0).document.getText()}${EOL}${EOL}${EOL}# Tests${EOL}${EOL}${notebook.cellAt(1).document.getText()}`;1148await Promise.race([testTimeoutPromise, assertPythonCodeIsValid(accessor, codeWithTests, 'Generated Code with Tests is not valid', 'Generated code did not pass the tests')]);1149}1150}1151}1152]1153}).finally(() => disposables.dispose());1154});1155}1156});11571158async function assertPythonCodeIsValid(accessor: ITestingServicesAccessor, pythonCode: string, validationMessage: string, executionMessage: string): Promise<void> {1159const cellIsValid = await isValidPythonFile(accessor, pythonCode);1160assert.ok(cellIsValid, `${validationMessage}:${EOL}${pythonCode}`);11611162const cellExecutesWithoutErrors = await canExecutePythonCodeWithoutErrors(accessor, pythonCode);1163assert.ok(cellExecutesWithoutErrors, `${executionMessage}:${EOL}${pythonCode}`);1164}11651166async function getNotebookCellDiagnostics(accessor: ITestingServicesAccessor, workspace: SimulationWorkspace, method: DiagnosticProviderId | DiagnosticsProvider): Promise<ITestDiagnostic[]> {1167const files = workspace.documents.filter(doc => doc.document.uri.scheme === Schemas.vscodeNotebookCell).map(doc => ({ fileName: workspace.getFilePath(doc.document.uri), fileContents: doc.document.getText() }));1168if (typeof method === 'string') {1169return await getDiagnostics(accessor, files, method);1170} else {1171return await method.getDiagnostics(accessor, files);1172}1173}11741175async function assertNoCellDiagnosticsAsync(accessor: ITestingServicesAccessor, outcome: IOutcome, workspace: SimulationWorkspace, method: DiagnosticProviderId | DiagnosticsProvider) {1176assert.strictEqual(outcome.type, 'inlineEdit');1177const diagnostics = await getNotebookCellDiagnostics(accessor, workspace, method);1178if (diagnostics.length > 0) {1179for (const diagnostic of diagnostics) {1180if (diagnostic.message.indexOf('indent') !== -1) {1181outcome.annotations.push({ label: 'indentation', message: diagnostic.message, severity: 'warning' });1182}1183}1184}1185assert.deepStrictEqual(diagnostics.length, 0, JSON.stringify(diagnostics, undefined, 2));1186}118711881189