-
Notifications
You must be signed in to change notification settings - Fork 408
FDK testkit #549
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
FDK testkit #549
Changes from all commits
70942f0
94a8e38
434103e
1cf70d6
ec9ecdc
4e092e5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,4 @@ | ||
| package tests | ||
| package test | ||
|
|
||
| import ( | ||
| "context" | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,77 @@ | ||
| Testing FDK-based functions | ||
| =========================== | ||
|
|
||
| Function development kit (FDK) as a piece of software that helps to write hot functions by encapsulating logic of processing incoming requests with respect to defined protocol (HTTP, JSON). | ||
| Particular testing framework help developers to identify if any programming language-specific FDK compatible with Fn hot formats. | ||
|
|
||
|
|
||
| Prerequisites | ||
| ------------- | ||
|
|
||
| This testing framework allows to run FDK tests against live Fn service, to let tests know of where Fn service is hosted please set following environment variable: | ||
| ```bash | ||
| export API_URL=http://fn.io:8080 | ||
| ``` | ||
|
|
||
| As an alternative test suite capable to bootstrap its own copy of Fn service locally, for this particular case following environment variables must be set: | ||
| ```bash | ||
| export DOCKER_HOST=/var/run/docker.sock | ||
| ``` | ||
| or wherever Docker Remote API daemon listens. | ||
|
|
||
| Test suite requires general purpose programming language-specific FDK-based function image that must be developed specifically for this test suite, following environment variable must be set: | ||
| ```bash | ||
| export FDK_FUNCTION_IMAGE="..." | ||
| ``` | ||
| This environment variable should contain a reference to the particular docker image. | ||
|
|
||
|
|
||
| Test suite details | ||
| ------------------ | ||
|
|
||
| Test suite contains following tests: | ||
|
|
||
| 1. `TestFDKFormatSmallBody` | ||
|
|
||
| `TestFDKFormatSmallBody` test | ||
| -------------------------- | ||
|
|
||
| FDK should support following formats: | ||
|
|
||
| - HTTP (subtest: `test-fdk-http-small-body`) | ||
| - JSON (subtest: `test-fdk-json-small-body`) | ||
|
|
||
| Request input body: | ||
| ```json | ||
| { | ||
| "name": "John" | ||
| } | ||
| ``` | ||
|
|
||
| Response output body: | ||
| ```text | ||
| Hello John | ||
| ``` | ||
|
|
||
| How to run tests? | ||
| ----------------- | ||
|
|
||
| There are couple options: from source code, from release binary | ||
|
|
||
| From source code: | ||
| ```bash | ||
| go test -v ./... | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does this test need to be go based?
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. if you want to write a new test you should be aware how to write tests in Go.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. i think it's fine if they're go-based so long as users don't need to have go installed to run the tests (put go tests in an image, let them specify fdk image to test with env vars or so to run tests)
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Agreed, PR that i've opened contains dockerfile, it's really helpful, indeed. |
||
| ``` | ||
|
|
||
| How to build test binary executable? | ||
| ------------------------------------ | ||
|
|
||
| Regular `go build` does not work with tests, so following command will create a binary executable for this particular test suite: | ||
| ```bash | ||
| go test -c -i | ||
| ``` | ||
|
|
||
| Sample FDK-based functions | ||
| -------------------------- | ||
|
|
||
| As an example test suite supplied with general purpose test [function written with FDK Python](./functions/python). | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,104 @@ | ||
| package fdk_test_kit | ||
|
|
||
| import ( | ||
| "bytes" | ||
| "encoding/json" | ||
| "net/url" | ||
| "path" | ||
| "strconv" | ||
| "strings" | ||
| "testing" | ||
|
|
||
| "fmt" | ||
| fnTest "github.com/fnproject/fn/test" | ||
| "net/http" | ||
| "os" | ||
| ) | ||
|
|
||
| type JSONResponse struct { | ||
| Message string `json:"message"` | ||
| } | ||
|
|
||
| func runAndAssert(t *testing.T, s *fnTest.SuiteSetup, fnRoute, fnImage, | ||
| fnFormat string, requestPayload interface{}, responsePayload interface{}) (*bytes.Buffer, *http.Response) { | ||
|
|
||
| fnTest.CreateApp(t, s.Context, s.Client, s.AppName, map[string]string{}) | ||
| fnTest.CreateRoute(t, s.Context, s.Client, s.AppName, fnRoute, fnImage, "sync", | ||
| fnFormat, s.RouteConfig, s.RouteHeaders) | ||
|
|
||
| u := url.URL{ | ||
| Scheme: "http", | ||
| Host: fnTest.Host(), | ||
| } | ||
| u.Path = path.Join(u.Path, "r", s.AppName, fnRoute) | ||
|
|
||
| b, _ := json.Marshal(requestPayload) | ||
| content := bytes.NewBuffer(b) | ||
| output := &bytes.Buffer{} | ||
|
|
||
| response, err := fnTest.CallFN(u.String(), content, output, "POST", []string{}) | ||
|
|
||
| if err != nil { | ||
| t.Errorf("Got unexpected error: %v", err) | ||
| } | ||
| json.Unmarshal(output.Bytes(), responsePayload) | ||
|
|
||
| fnTest.DeleteApp(t, s.Context, s.Client, s.AppName) | ||
|
|
||
| return output, response | ||
|
|
||
| } | ||
|
|
||
| func TestFDKFormatSmallBody(t *testing.T) { | ||
|
|
||
| FDKImage := os.Getenv("FDK_FUNCTION_IMAGE") | ||
| if FDKImage == "" { | ||
| t.Error("Please set FDK-based function image to test") | ||
| } | ||
| formats := []string{"http", "json"} | ||
|
|
||
| helloJohnPayload := &struct { | ||
| Name string `json:"name"` | ||
| }{ | ||
| Name: "Jimmy", | ||
| } | ||
| helloJohnExpectedOutput := "Hello Jimmy" | ||
| for _, format := range formats { | ||
|
|
||
| // echo function: | ||
| // payload: | ||
| // { | ||
| // "name": "John" | ||
| // } | ||
| // response: | ||
| // "Hello John" | ||
| t.Run(fmt.Sprintf("test-fdk-%v-small-body", format), func(t *testing.T) { | ||
|
|
||
| t.Parallel() | ||
| s := fnTest.SetupDefaultSuite() | ||
| route := fmt.Sprintf("/test-fdk-%v-format-small-body", format) | ||
|
|
||
| responsePayload := &JSONResponse{} | ||
| output, response := runAndAssert(t, s, route, FDKImage, format, helloJohnPayload, responsePayload) | ||
|
|
||
| if !strings.Contains(helloJohnExpectedOutput, responsePayload.Message) { | ||
| t.Errorf("Output assertion error.\n\tExpected: %v\n\tActual: %v", helloJohnExpectedOutput, output.String()) | ||
| } | ||
| if response.StatusCode != 200 { | ||
| t.Errorf("Status code assertion error.\n\tExpected: %v\n\tActual: %v", 200, response.StatusCode) | ||
| } | ||
|
|
||
| expectedHeaderNames := []string{"Content-Type", "Content-Length"} | ||
| expectedHeaderValues := []string{"text/plain; charset=utf-8", strconv.Itoa(output.Len())} | ||
| for i, name := range expectedHeaderNames { | ||
| actual := response.Header.Get(name) | ||
| expected := expectedHeaderValues[i] | ||
| if !strings.Contains(expected, actual) { | ||
| t.Errorf("HTTP header assertion error for %v."+ | ||
| "\n\tExpected: %v\n\tActual: %v", name, expected, actual) | ||
| } | ||
| } | ||
|
|
||
| }) | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| FROM python:3.6.2 | ||
|
|
||
|
|
||
| RUN mkdir /code | ||
| ADD . /code/ | ||
| WORKDIR /code/ | ||
| RUN pip install -r requirements.txt | ||
| WORKDIR /code/ | ||
|
|
||
| ENTRYPOINT ["python3", "func.py"] |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,44 @@ | ||
| # All Rights Reserved. | ||
| # | ||
| # 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. | ||
|
|
||
| import fdk | ||
| import ujson | ||
|
|
||
| from fdk.http import request as http_request | ||
|
|
||
|
|
||
| def handler(context, data=None, loop=None): | ||
|
|
||
| # specifically for http protocol | ||
| if isinstance(data, (http_request.ChunkedStream, | ||
| http_request.ContentLengthStream)): | ||
| data = data.readall() | ||
|
|
||
| # specifically for json protocol | ||
| if isinstance(data, (bytes, str)): | ||
| if isinstance(data, bytes): | ||
| data = data.decode("utf-8") | ||
| try: | ||
| data = ujson.loads(data) | ||
| except Exception: | ||
| data = {} | ||
|
|
||
| name = data.get("name") | ||
| if not len(name): | ||
| name = "World" | ||
| return "Hello {}".format(name) | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| fdk.handle(handler) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| name: fnproject/test-fdk-python | ||
| version: 0.0.1 | ||
| runtime: python | ||
| format: http | ||
| timeout: 100 | ||
| path: /test-fdk-python |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| fdk==0.0.4 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| package fdk_test_kit | ||
|
|
||
| import ( | ||
| "fmt" | ||
| "os" | ||
| "testing" | ||
|
|
||
| fnTest "github.com/fnproject/fn/test" | ||
| ) | ||
|
|
||
| func TestMain(m *testing.M) { | ||
| // call flag.Parse() here if TestMain uses flags | ||
| s := fnTest.SetupDefaultSuite() | ||
| result := m.Run() | ||
| fnTest.Cleanup() | ||
| s.Cancel() | ||
| if result == 0 { | ||
| fmt.Fprintln(os.Stdout, "😀 👍 🎗") | ||
| } | ||
| os.Exit(result) | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think we should force this, FDK can decide which they want. I don't plan on supporting HTTP in Ruby or Node libs for instance.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As long as Fn gives protocol options all FDKs must be compatible.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
agree w/ travis if we can have a nice way for tests just to say 'doesn't work with http' then a lot of fdks may be okay with that (we could still consider them 'compliant'?). it would be nice if images/code could expose their capabilities so that coordination wasn't needed between function configuration of format and the code itself, not sure how this would look, and somewhat unrelated to this patch.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's the point. We don't have criteria for compliance. If FDK supports only default format (which is not the case, because you don't need an FDK to work with default format) would that make FDK compliant? If FDK supports only one format distinct from the default (which is the case of java, go, ruby FDKs) does it make FDK compliant?
From my point of view, I do want tests to say that FDK is compatible/compliant if it implements all supported formats.
If we go the road of "compliant if supports at least one hot protocol" then we need to define the strategy for FDKs how to reject unsupported protocols, for instance: HTTP 501 Not Implemented (Unsupported protocol). This response should be mandatory in order to make tests keep going on because not implemented protocol is not an error in FDK internal parts rather than the choice of developer.
cc @zootalures
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i have some ideas about compliance if you want to start somewhere to collab on that, I'll poop the ideas out here:
FN_FORMATenv var changingmaybe missing something, but overall they're pretty simple and that seems like a good start at least. if at the end we make sure >=1 hot format works and then thumbs up, that seems doable?
+1. it would be kinda sweet if they could back channel which formats would work and fn could just switch to that (it seems possible, at least, but unrelated to this work).