Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Django and Flask template debugging tests #1317

Merged
merged 13 commits into from
Apr 6, 2018
4 changes: 2 additions & 2 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@ before_install: |
yarn global add azure-cli
export TRAVIS_PYTHON_PATH=`which python`
install:
- pip install --upgrade -r requirements.txt
- pip install -t ./pythonFiles/experimental/ptvsd git+https://github.com/Microsoft/ptvsd/
- python -m pip install --upgrade -r requirements.txt
- python -m pip install -t ./pythonFiles/experimental/ptvsd git+https://github.com/Microsoft/ptvsd/
- yarn

script:
Expand Down
2 changes: 2 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,5 @@ pytest
fabric
numba
rope
flask
django
11 changes: 11 additions & 0 deletions src/test/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import { IS_MULTI_ROOT_TEST } from './initialize';
const fileInNonRootWorkspace = path.join(__dirname, '..', '..', 'src', 'test', 'pythonFiles', 'dummy.py');
export const rootWorkspaceUri = getWorkspaceRoot();

export const PYTHON_PATH = getPythonPath();

export type PythonSettingKeys = 'workspaceSymbols.enabled' | 'pythonPath' |
'linting.lintOnSave' |
'linting.enabled' | 'linting.pylintEnabled' |
Expand Down Expand Up @@ -118,3 +120,12 @@ const globalPythonPathSetting = workspace.getConfiguration('python').inspect('py
export const clearPythonPathInWorkspaceFolder = async (resource: string | Uri) => retryAsync(setPythonPathInWorkspace)(resource, ConfigurationTarget.WorkspaceFolder);
export const setPythonPathInWorkspaceRoot = async (pythonPath: string) => retryAsync(setPythonPathInWorkspace)(undefined, ConfigurationTarget.Workspace, pythonPath);
export const resetGlobalPythonPathSetting = async () => retryAsync(restoreGlobalPythonPathSetting)();

function getPythonPath(): string {
// tslint:disable-next-line:no-unsafe-any
if (process.env.TRAVIS_PYTHON_PATH && fs.existsSync(process.env.TRAVIS_PYTHON_PATH)) {
// tslint:disable-next-line:no-unsafe-any
return process.env.TRAVIS_PYTHON_PATH;
}
return 'python';
}
3 changes: 3 additions & 0 deletions src/test/debugger/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,9 @@ export async function validateVariablesInFrame(debugClient: DebugClient,
export function makeHttpRequest(uri: string): Promise<string> {
return new Promise<string>((resolve, reject) => {
request.get(uri, (error: any, response: request.Response, body: any) => {
if (error) {
return reject(error);
}
if (response.statusCode !== 200) {
reject(new Error(`Status code = ${response.statusCode}`));
} else {
Expand Down
148 changes: 148 additions & 0 deletions src/test/debugger/web.framework.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

'use strict';

// tslint:disable:no-suspicious-comment max-func-body-length no-invalid-this no-var-requires no-require-imports no-any no-http-string no-string-literal no-console

import { expect } from 'chai';
import * as getFreePort from 'get-port';
import * as path from 'path';
import { DebugClient } from 'vscode-debugadapter-testsupport';
import { EXTENSION_ROOT_DIR } from '../../client/common/constants';
import { noop } from '../../client/common/core.utils';
import { DebugOptions, LaunchRequestArguments } from '../../client/debugger/Common/Contracts';
import { PYTHON_PATH, sleep } from '../common';
import { IS_MULTI_ROOT_TEST, TEST_DEBUGGER } from '../initialize';
import { DEBUGGER_TIMEOUT } from './common/constants';
import { continueDebugging, createDebugAdapter, ExpectedVariable, hitHttpBreakpoint, makeHttpRequest, validateVariablesInFrame } from './utils';

let testCounter = 0;
const debuggerType = 'pythonExperimental';
suite(`Django and Flask Debugging: ${debuggerType}`, () => {
let debugClient: DebugClient;
setup(async function () {
if (!IS_MULTI_ROOT_TEST || !TEST_DEBUGGER) {
this.skip();
}
this.timeout(5 * DEBUGGER_TIMEOUT);
const coverageDirectory = path.join(EXTENSION_ROOT_DIR, `debug_coverage_django_flask${testCounter += 1}`);
debugClient = await createDebugAdapter(coverageDirectory);
});
teardown(async () => {
// Wait for a second before starting another test (sometimes, sockets take a while to get closed).
await sleep(1000);
try {
await debugClient.stop().catch(noop);
// tslint:disable-next-line:no-empty
} catch (ex) { }
await sleep(1000);
});
function buildLaunchArgs(workspaceDirectory: string): LaunchRequestArguments {
const env = {};
// tslint:disable-next-line:no-string-literal
env['PYTHONPATH'] = path.join(EXTENSION_ROOT_DIR, 'pythonFiles', 'experimental', 'ptvsd');

// tslint:disable-next-line:no-unnecessary-local-variable
const options: LaunchRequestArguments = {
cwd: workspaceDirectory,
program: '',
debugOptions: [DebugOptions.RedirectOutput],
pythonPath: PYTHON_PATH,
args: [],
env,
envFile: '',
logToFile: true,
type: debuggerType
};

return options;
}
async function buildFlaskLaunchArgs(workspaceDirectory: string) {
const port = await getFreePort({ host: 'localhost' });
const options = buildLaunchArgs(workspaceDirectory);

options.env!['FLASK_APP'] = path.join(workspaceDirectory, 'run.py');
options.module = 'flask';
options.debugOptions = [DebugOptions.RedirectOutput, DebugOptions.Jinja];
options.args = [
'run',
'--no-debugger',
'--no-reload',
'--port',
`${port}`
];

return { options, port };
}
async function buildDjangoLaunchArgs(workspaceDirectory: string) {
const port = await getFreePort({ host: 'localhost' });
const options = buildLaunchArgs(workspaceDirectory);

options.program = path.join(workspaceDirectory, 'manage.py');
options.debugOptions = [DebugOptions.RedirectOutput, DebugOptions.Django];
options.args = [
'runserver',
'--noreload',
'--nothreading',
`${port}`
];

return { options, port };
}

async function testTemplateDebugging(launchArgs: LaunchRequestArguments, port: number, viewFile: string, viewLine: number, templateFile: string, templateLine: number) {
await Promise.all([
debugClient.configurationSequence(),
debugClient.launch(launchArgs),
debugClient.waitForEvent('initialized'),
debugClient.waitForEvent('process'),
debugClient.waitForEvent('thread')
]);

const httpResult = await makeHttpRequest(`http://localhost:${port}`);

expect(httpResult).to.contain('Hello this_is_a_value_from_server');
expect(httpResult).to.contain('Hello this_is_another_value_from_server');

await hitHttpBreakpoint(debugClient, `http://localhost:${port}`, viewFile, viewLine);

await continueDebugging(debugClient);
await debugClient.setBreakpointsRequest({ breakpoints: [], lines: [], source: { path: viewFile } });

// Template debugging.
const [stackTrace, htmlResultPromise] = await hitHttpBreakpoint(debugClient, `http://localhost:${port}`, templateFile, templateLine);

// Wait for breakpoint to hit
const expectedVariables: ExpectedVariable[] = [
{ name: 'value_from_server', type: 'str', value: '\'this_is_a_value_from_server\'' },
{ name: 'another_value_from_server', type: 'str', value: '\'this_is_another_value_from_server\'' }
];
await validateVariablesInFrame(debugClient, stackTrace, expectedVariables, 1);

await debugClient.setBreakpointsRequest({ breakpoints: [], lines: [], source: { path: templateFile } });
await continueDebugging(debugClient);

const htmlResult = await htmlResultPromise;
expect(htmlResult).to.contain('Hello this_is_a_value_from_server');
expect(htmlResult).to.contain('Hello this_is_another_value_from_server');
}

test('Test Flask Route and Template debugging', async () => {
const workspaceDirectory = path.join(EXTENSION_ROOT_DIR, 'src', 'testMultiRootWkspc', 'workspace5', 'flaskApp');
const { options, port } = await buildFlaskLaunchArgs(workspaceDirectory);

await testTemplateDebugging(options, port,
path.join(workspaceDirectory, 'run.py'), 7,
path.join(workspaceDirectory, 'templates', 'index.html'), 6);
});

test('Test Django Route and Template debugging', async () => {
const workspaceDirectory = path.join(EXTENSION_ROOT_DIR, 'src', 'testMultiRootWkspc', 'workspace5', 'djangoApp');
const { options, port } = await buildDjangoLaunchArgs(workspaceDirectory);

await testTemplateDebugging(options, port,
path.join(workspaceDirectory, 'home', 'views.py'), 10,
path.join(workspaceDirectory, 'home', 'templates', 'index.html'), 6);
});
});
13 changes: 1 addition & 12 deletions src/test/initialize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import * as path from 'path';
import * as vscode from 'vscode';
import { PythonSettings } from '../client/common/configSettings';
import { activated } from '../client/extension';
import { clearPythonPathInWorkspaceFolder, resetGlobalPythonPathSetting, setPythonPathInWorkspaceRoot } from './common';
import { clearPythonPathInWorkspaceFolder, PYTHON_PATH, resetGlobalPythonPathSetting, setPythonPathInWorkspaceRoot } from './common';

export * from './constants';

Expand All @@ -16,8 +16,6 @@ const workspace3Uri = vscode.Uri.file(path.join(multirootPath, 'workspace3'));
//First thing to be executed.
process.env['VSC_PYTHON_CI_TEST'] = '1';

const PYTHON_PATH = getPythonPath();

// Ability to use custom python environments for testing
export async function initializePython() {
await resetGlobalPythonPathSetting();
Expand Down Expand Up @@ -48,12 +46,3 @@ export async function closeActiveWindows(): Promise<void> {
// tslint:disable-next-line:no-unnecessary-callback-wrapper
.then(() => resolve(), reject));
}

function getPythonPath(): string {
// tslint:disable-next-line:no-unsafe-any
if (process.env.TRAVIS_PYTHON_PATH && fs.existsSync(process.env.TRAVIS_PYTHON_PATH)) {
// tslint:disable-next-line:no-unsafe-any
return process.env.TRAVIS_PYTHON_PATH;
}
return 'python';
}
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<!doctype html>
<html>
<body>

<h1>Hello {{ value_from_server }}!</h1>
<h1>Hello {{ another_value_from_server }}!</h1>

</body>
</html>
7 changes: 7 additions & 0 deletions src/testMultiRootWkspc/workspace5/djangoApp/home/urls.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from django.conf.urls import url

from . import views

urlpatterns = [
url('', views.index, name='index'),
]
10 changes: 10 additions & 0 deletions src/testMultiRootWkspc/workspace5/djangoApp/home/views.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from django.shortcuts import render
from django.template import loader


def index(request):
context = {
'value_from_server':'this_is_a_value_from_server',
'another_value_from_server':'this_is_another_value_from_server'
}
return render(request, 'index.html', context)
22 changes: 22 additions & 0 deletions src/testMultiRootWkspc/workspace5/djangoApp/manage.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#!/usr/bin/env python
import os
import sys

if __name__ == "__main__":
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "mysite.settings")
try:
from django.core.management import execute_from_command_line
except ImportError:
# The above import may fail for some other reason. Ensure that the
# issue is really that Django is missing to avoid masking other
# exceptions on Python 2.
try:
import django
except ImportError:
raise ImportError(
"Couldn't import Django. Are you sure it's installed and "
"available on your PYTHONPATH environment variable? Did you "
"forget to activate a virtual environment?"
)
raise
execute_from_command_line(sys.argv)
Empty file.
93 changes: 93 additions & 0 deletions src/testMultiRootWkspc/workspace5/djangoApp/mysite/settings.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
"""
Django settings for mysite project.

Generated by 'django-admin startproject' using Django 1.11.2.

For more information on this file, see
https://docs.djangoproject.com/en/1.11/topics/settings/

For the full list of settings and their values, see
https://docs.djangoproject.com/en/1.11/ref/settings/
"""

import os

# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))


# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/1.11/howto/deployment/checklist/

# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = '5u06*)07dvd+=kn)zqp8#b0^qt@*$8=nnjc&&0lzfc28(wns&l'

# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True

ALLOWED_HOSTS = ['localhost', '127.0.0.1']


# Application definition

INSTALLED_APPS = [
'django.contrib.contenttypes',
'django.contrib.messages',
'django.contrib.staticfiles',
]

MIDDLEWARE = [
]

ROOT_URLCONF = 'mysite.urls'

TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': ['home/templates'],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.messages.context_processors.messages',
],
},
},
]

WSGI_APPLICATION = 'mysite.wsgi.application'


# Database
# https://docs.djangoproject.com/en/1.11/ref/settings/#databases

DATABASES = {
}


# Password validation
# https://docs.djangoproject.com/en/1.11/ref/settings/#auth-password-validators

AUTH_PASSWORD_VALIDATORS = [
]


# Internationalization
# https://docs.djangoproject.com/en/1.11/topics/i18n/

LANGUAGE_CODE = 'en-us'

TIME_ZONE = 'UTC'

USE_I18N = True

USE_L10N = True

USE_TZ = True


# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/1.11/howto/static-files/

STATIC_URL = '/static/'
23 changes: 23 additions & 0 deletions src/testMultiRootWkspc/workspace5/djangoApp/mysite/urls.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
"""mysite URL Configuration

The `urlpatterns` list routes URLs to views. For more information please see:
https://docs.djangoproject.com/en/1.11/topics/http/urls/
Examples:
Function views
1. Add an import: from my_app import views
2. Add a URL to urlpatterns: url(r'^$', views.home, name='home')
Class-based views
1. Add an import: from other_app.views import Home
2. Add a URL to urlpatterns: url(r'^$', Home.as_view(), name='home')
Including another URLconf
1. Import the include() function: from django.conf.urls import url, include
2. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls'))
"""
from django.conf.urls import url, include
from django.contrib import admin
from django.views.generic import RedirectView

urlpatterns = [
url(r'^home/', include('home.urls')),
url('', RedirectView.as_view(url='/home/')),
]
Loading