Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 74 additions & 0 deletions vscode/extension/tests/no_lsp.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { expect, test } from '@playwright/test'
import fs from 'fs-extra'
import os from 'os'
import path from 'path'
import {
createVirtualEnvironment,
pipInstall,
REPO_ROOT,
startVSCode,
SUSHI_SOURCE_PATH,
} from './utils'

test('missing LSP dependencies shows install prompt', async ({}, testInfo) => {
testInfo.setTimeout(120_000) // 2 minutes for venv creation and package installation
const tempDir = await fs.mkdtemp(
path.join(os.tmpdir(), 'vscode-test-tcloud-'),
)
const pythonEnvDir = path.join(tempDir, '.venv')
const pythonDetails = await createVirtualEnvironment(pythonEnvDir)
const custom_materializations = path.join(
REPO_ROOT,
'examples',
'custom_materializations',
)
const sqlmeshWithExtras = `${REPO_ROOT}[bigquery]`
await pipInstall(pythonDetails, [sqlmeshWithExtras, custom_materializations])

try {
// Copy sushi project
await fs.copy(SUSHI_SOURCE_PATH, tempDir)

// Configure VS Code settings to use our Python environment
const settings = {
'python.defaultInterpreterPath': pythonDetails.pythonPath,
'sqlmesh.environmentPath': pythonEnvDir,
}
await fs.ensureDir(path.join(tempDir, '.vscode'))
await fs.writeJson(
path.join(tempDir, '.vscode', 'settings.json'),
settings,
{ spaces: 2 },
)

// Start VS Code
const { window, close } = await startVSCode(tempDir)

// Open a SQL file to trigger SQLMesh activation
// Wait for the models folder to be visible
await window.waitForSelector('text=models')

// Click on the models folder
await window
.getByRole('treeitem', { name: 'models', exact: true })
.locator('a')
.click()

// Open the top_waiters model
await window
.getByRole('treeitem', { name: 'customers.sql', exact: true })
.locator('a')
.click()

// Wait for the message to show that LSP extras need to be installed
await window.waitForSelector('text=LSP dependencies missing')
expect(await window.locator('text=Install').count()).toBeGreaterThanOrEqual(
1,
)

await close()
} finally {
// Clean up
await fs.remove(tempDir)
}
})
45 changes: 16 additions & 29 deletions vscode/extension/tests/tcloud.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,48 +2,35 @@ import { expect, test } from '@playwright/test'
import path from 'path'
import fs from 'fs-extra'
import os from 'os'
import { exec } from 'child_process'
import { promisify } from 'util'
import { REPO_ROOT, startVSCode, SUSHI_SOURCE_PATH } from './utils'

const execAsync = promisify(exec)
import {
createVirtualEnvironment,
pipInstall,
REPO_ROOT,
startVSCode,
SUSHI_SOURCE_PATH,
} from './utils'

/**
* Helper function to create and set up a Python virtual environment
*/
async function setupPythonEnvironment(envDir: string): Promise<string> {
// Create virtual environment
const pythonCmd = process.platform === 'win32' ? 'python' : 'python3'
const { stderr } = await execAsync(`${pythonCmd} -m venv "${envDir}"`)
if (stderr && !stderr.includes('WARNING')) {
throw new Error(`Failed to create venv: ${stderr}`)
}

// Get paths
const isWindows = process.platform === 'win32'
const binDir = path.join(envDir, isWindows ? 'Scripts' : 'bin')
const pythonPath = path.join(binDir, isWindows ? 'python.exe' : 'python')
const pipPath = path.join(binDir, isWindows ? 'pip.exe' : 'pip')
const pythonDetails = await createVirtualEnvironment(envDir)

// Install the mock tcloud package
const mockTcloudPath = path.join(__dirname, 'tcloud')
const { stderr: pipErr1 } = await execAsync(
`"${pipPath}" install -e "${mockTcloudPath}"`,
)
if (pipErr1 && !pipErr1.includes('WARNING') && !pipErr1.includes('notice')) {
throw new Error(`Failed to install mock tcloud: ${pipErr1}`)
}
await pipInstall(pythonDetails, [mockTcloudPath])

// Install sqlmesh from the local repository with LSP support
const sqlmeshRepoPath = path.join(__dirname, '..', '..', '..') // Navigate to repo root from tests dir
const { stderr: pipErr2 } = await execAsync(
`"${pipPath}" install -e "${sqlmeshRepoPath}[lsp,bigquery]" "${REPO_ROOT}/examples/custom_materializations"`,
const customMaterializations = path.join(
REPO_ROOT,
'examples',
'custom_materializations',
)
if (pipErr2 && !pipErr2.includes('WARNING') && !pipErr2.includes('notice')) {
throw new Error(`Failed to install sqlmesh: ${pipErr2}`)
}
const sqlmeshWithExtras = `${REPO_ROOT}[lsp,bigquery]`
await pipInstall(pythonDetails, [sqlmeshWithExtras, customMaterializations])

return pythonPath
return pythonDetails.pythonPath
}

/**
Expand Down
50 changes: 50 additions & 0 deletions vscode/extension/tests/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import path from 'path'
import fs from 'fs-extra'
import os from 'os'
import { _electron as electron, Page } from '@playwright/test'
import { exec } from 'child_process'
import { promisify } from 'util'

// Absolute path to the VS Code executable you downloaded in step 1.
export const VS_CODE_EXE = fs.readJsonSync(
Expand Down Expand Up @@ -87,3 +89,51 @@ export const clickExplorerTab = async (page: Page): Promise<void> => {
await page.locator("text='Explorer'").waitFor({ state: 'visible' })
}
}

const execAsync = promisify(exec)

export interface PythonEnvironment {
pythonPath: string
pipPath: string
}

/**
* Create a virtual environment in the given directory.
* @param venvDir The directory to create the virtual environment in.
*/
export const createVirtualEnvironment = async (
venvDir: string,
): Promise<PythonEnvironment> => {
const pythonCmd = process.platform === 'win32' ? 'python' : 'python3'
const { stderr } = await execAsync(`${pythonCmd} -m venv "${venvDir}"`)
if (stderr && !stderr.includes('WARNING')) {
throw new Error(`Failed to create venv: ${stderr}`)
}
// Get paths
const isWindows = process.platform === 'win32'
const binDir = path.join(venvDir, isWindows ? 'Scripts' : 'bin')
const pythonPath = path.join(binDir, isWindows ? 'python.exe' : 'python')
const pipPath = path.join(binDir, isWindows ? 'pip.exe' : 'pip')

return {
pythonPath,
pipPath,
}
}

/**
* Install packages in the given virtual environment.
* @param pythonDetails The Python environment to use.
* @param packagePaths The paths to the packages to install (string[]).
*/
export const pipInstall = async (
pythonDetails: PythonEnvironment,
packagePaths: string[],
): Promise<void> => {
const { pipPath } = pythonDetails
const execString = `"${pipPath}" install -e "${packagePaths.join('" -e "')}"`
const { stderr } = await execAsync(execString)
if (stderr && !stderr.includes('WARNING') && !stderr.includes('notice')) {
throw new Error(`Failed to install package: ${stderr}`)
}
}