Skip to content
Closed
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
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
.idea/
*.sublime*
*.swp
functions
iron.json
dj.config.json
dj.cluster.*.json
Expand Down Expand Up @@ -30,3 +29,4 @@ fnlb/fnlb
/fn
.DS_Store
/fnserver
fdk-test-kit.test
2 changes: 1 addition & 1 deletion test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ export POSTGRES_URL="postgres://postgres:root@${POSTGRES_HOST}:${POSTGRES_PORT}/
export MYSQL_URL="mysql://root:root@tcp(${MYSQL_HOST}:${MYSQL_PORT})/funcs"
export MINIO_URL="s3://admin:password@${MINIO_HOST}:${MINIO_PORT}/us-east-1/fnlogs"

go test -v $(go list ./... | grep -v vendor | grep -v examples | grep -v test/fn-api-tests)
go test -v $(go list ./... | grep -v vendor | grep -v examples | grep -v test/fn-api-tests | grep -v test/fdk-test-kit)
go vet -v $(go list ./... | grep -v vendor)
docker rm --force func-postgres-test
docker rm --force func-mysql-test
Expand Down
2 changes: 1 addition & 1 deletion test/fn-api-tests/apps_api.go → test/apps_api.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package tests
package test

import (
"context"
Expand Down
77 changes: 77 additions & 0 deletions test/fdk-test-kit/README.md
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`)
Copy link
Copy Markdown
Contributor

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.

Copy link
Copy Markdown
Member Author

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.

Copy link
Copy Markdown
Contributor

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.

Copy link
Copy Markdown
Member Author

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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't have criteria for compliance.

i have some ideas about compliance if you want to start somewhere to collab on that, I'll poop the ideas out here:

  • runs > 1 call in same image [hot], serially
  • [eventually] times out a call properly (i.e. takes input of next call when expected)
  • parses headers for the set of expected headers, e.g. can output correct CALL_ID on N runs
  • certain vars accessible from env (make sure no masking of env)
  • supports default and >= 1 hot format based on FN_FORMAT env var changing
  • ideally, the code looks a certain way, but this requires humans or so-much-static-analysis-it's-not-worth-it in 2017. (i.e. want to make sure they have a 'context' variable that has config, headers on it)

maybe 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?

then we need to define the strategy for FDKs how to reject unsupported protocols,

+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).

- 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 ./...
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this test need to be go based?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The 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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The 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)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The 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).
104 changes: 104 additions & 0 deletions test/fdk-test-kit/formats_test.go
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)
}
}

})
}
}
10 changes: 10 additions & 0 deletions test/fdk-test-kit/functions/python/Dockerfile
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"]
44 changes: 44 additions & 0 deletions test/fdk-test-kit/functions/python/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)
6 changes: 6 additions & 0 deletions test/fdk-test-kit/functions/python/func.yaml
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
1 change: 1 addition & 0 deletions test/fdk-test-kit/functions/python/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
fdk==0.0.4
21 changes: 21 additions & 0 deletions test/fdk-test-kit/init_test.go
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)
}
Loading