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

chore: add python/pydevd tests for python 3.9 #100

Merged
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
18 changes: 18 additions & 0 deletions python/skaffold.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,24 @@ profiles:

# integration: set of `skaffold debug`-like integration tests
- name: integration
patches:
- op: add
path: /build/artifacts/-
value:
image: python39app
context: test/pythonapp
docker:
buildArgs:
PYTHONVERSION: "3.9"
- op: add
path: /build/artifacts/-
value:
image: pydevconnect
context: test/pydevconnect
deploy:
kubectl:
manifests:
- test/k8s-test-pydevd-python39.yaml

# release: pushes images to production with :latest
- name: release
Expand Down
77 changes: 77 additions & 0 deletions python/test/k8s-test-pydevd-python39.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
apiVersion: v1
kind: Pod
metadata:
name: python39pod
labels:
app: hello
protocol: pydevd
runtime: python39
spec:
containers:
- name: python39app
image: python39app
command: ["/dbg/python/launcher", "--mode", "pydevd", "--port", "12345", "--"]
args: ["python", "-m", "flask", "run", "--host=0.0.0.0"]
ports:
- containerPort: 5000
- containerPort: 12345
name: pydevd
env:
- name: WRAPPER_VERBOSE
value: debug
readinessProbe:
httpGet:
path: /
port: 5000
volumeMounts:
- mountPath: /dbg
name: python-debugging-support
initContainers:
- image: skaffold-debug-python
name: install-python-support
resources: {}
volumeMounts:
- mountPath: /dbg
name: python-debugging-support
volumes:
- emptyDir: {}
name: python-debugging-support

---
apiVersion: v1
kind: Service
metadata:
name: hello-pydevd-python39
spec:
ports:
- name: http
port: 5000
protocol: TCP
- name: pydevd
port: 12345
protocol: TCP
selector:
app: hello
protocol: pydevd
runtime: python39

---
apiVersion: batch/v1
kind: Job
metadata:
name: connect-to-python39
labels:
project: container-debug-support
type: integration-test
spec:
ttlSecondsAfterFinished: 10
backoffLimit: 1
template:
spec:
restartPolicy: Never
containers:
- name: verify-python39
image: pydevconnect
args: ["hello-pydevd-python39:12345"]


25 changes: 25 additions & 0 deletions python/test/pydevconnect/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Copyright 2021 The Skaffold Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# This Dockerfile creates a test image for verifying that pydevd is
# running somewhere.

FROM golang:1.17 as build
COPY . .
RUN CGO_ENABLED=0 go build -o pydevconnect -ldflags '-s -w -extldflags "-static"' pydevconnect.go

# Now populate the test image
FROM busybox
COPY --from=build /go/pydevconnect /
ENTRYPOINT ["/pydevconnect"]
142 changes: 142 additions & 0 deletions python/test/pydevconnect/pydevconnect.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
/*
Copyright 2021 The Skaffold Authors

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

// Test utility to connect to a pydevd server to validate that it is working.
// Protocol: https://github.com/fabioz/PyDev.Debugger/blob/main/_pydevd_bundle/pydevd_comm.py
package main

import (
"bufio"
"fmt"
"log"
"net"
"net/url"
"os"
"strconv"
"strings"
"time"
)

const (
CMD_RUN = 101
CMD_LIST_THREADS = 102
CMD_THREAD_CREATE = 103
CMD_THREAD_KILL = 104
CMD_THREAD_RUN = 106
CMD_SET_BREAK = 111
CMD_WRITE_TO_CONSOLE = 116
CMD_VERSION = 501
CMD_RETURN = 502
CMD_ERROR = 901
)

func main() {
if len(os.Args) != 2 {
fmt.Printf("Check that pydevd is running.\n")
fmt.Printf("use: %s host:port\n", os.Args[0])
os.Exit(1)
}

var conn net.Conn
for i := 0; i < 60; i++ {
var err error
conn, err = net.Dial("tcp", os.Args[1])
if err == nil {
break
}
fmt.Printf("(sleeping) unable to connect to %s: %v\n", os.Args[1], err)
time.Sleep(2 * time.Second)
}

pydb := newPydevdDebugConnection(conn)

code, response := pydb.makeRequestWithResponse(CMD_VERSION, "pydevconnect")
if code != CMD_VERSION {
log.Fatalf("expected CMD_VERSION (%d) response (%q)", code, response)
}
if decoded, err := url.QueryUnescape(response); err != nil {
log.Fatalf("CMD_VERSION response (%q): decoding error: %v", response, err)
} else {
fmt.Printf("version: %s", decoded)
}

pydb.makeRequest(CMD_RUN, "test")
}

type pydevdDebugConnection struct {
conn net.Conn
reader *bufio.Reader
msgID int
}

func newPydevdDebugConnection(c net.Conn) *pydevdDebugConnection {
return &pydevdDebugConnection{
conn: c,
reader: bufio.NewReader(c),
msgID: 1,
}
}

func (c *pydevdDebugConnection) makeRequest(code int, arg string) {
currMsgID := c.msgID
c.msgID += 2 // outgoing requests should have odd msgID

fmt.Printf("Making request: code=%d msgId=%d arg=%q\n", code, currMsgID, arg)
fmt.Fprintf(c.conn, "%d\t%d\t%s\n", code, currMsgID, arg)
}

func (c *pydevdDebugConnection) makeRequestWithResponse(code int, arg string) (int, string) {
currMsgID := c.msgID
c.msgID += 2 // outgoing requests should have odd msgID

fmt.Printf("Making request: code=%d msgId=%d arg=%q\n", code, currMsgID, arg)
fmt.Fprintf(c.conn, "%d\t%d\t%s\n", code, currMsgID, arg)

for {
response, err := c.reader.ReadString('\n')
if err != nil {
log.Fatalf("error receiving response: %v", err)
}
fmt.Printf("Received response: %q\n", response)

// check response
tsv := strings.Split(response, "\t")
if len(tsv) != 3 {
log.Fatalf("invalid response: expecting three tab-separated components: %q", response)
}

code, err = strconv.Atoi(tsv[0])
if err != nil {
log.Fatalf("could not parse response code: %q", tsv[0])
}

responseID, err := strconv.Atoi(tsv[1])
if err != nil {
log.Fatalf("could not parse response ID: %q", tsv[1])
} else if responseID == currMsgID {
return code, tsv[2]
}

// handle commands sent to us
switch code {
case CMD_THREAD_CREATE:
fmt.Printf("CMD_THREAD_CREATE: %s\n", tsv[2:])

default:
log.Fatalf("Unknown/unhandled code %d: %q", code, tsv[2:])
}
}
}
15 changes: 15 additions & 0 deletions python/test/pythonapp/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
ARG PYTHONVERSION
FROM python:${PYTHONVERSION}

RUN pip install --upgrade pip

ARG DEBUG=0
ENV FLASK_DEBUG $DEBUG
ENV FLASK_APP=src/app.py
CMD ["python", "-m", "flask", "run", "--host=0.0.0.0"]

COPY requirements.txt .
ENV PATH="/home/python/.local/bin:${PATH}"
RUN pip install -r requirements.txt

COPY src src
1 change: 1 addition & 0 deletions python/test/pythonapp/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Flask
7 changes: 7 additions & 0 deletions python/test/pythonapp/src/app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from flask import Flask
app = Flask(__name__)

@app.route('/')
def hello_world():
print("incoming request")
return 'Hello, World from Flask!\n'