Skip to content

Commit 9709bc9

Browse files
authored
Add non-dockerized e2e tests. (#100)
- Also make read/writeConfig async
1 parent fc8e3ee commit 9709bc9

File tree

6 files changed

+185
-98
lines changed

6 files changed

+185
-98
lines changed

.github/workflows/ci.yaml

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -90,10 +90,15 @@ jobs:
9090
env:
9191
NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
9292
- name: Build docker images
93+
if: matrix.platform == 'ubuntu-latest'
9394
run: |
9495
cd ts/demo-functions
9596
npm run kpt:docker-build
96-
- name: Run e2e tests
97+
- name: Run all e2e tests
98+
if: matrix.platform == 'ubuntu-latest'
9799
run: |
98-
npm install -g bats
99-
bats tests/e2e.bats
100+
tests/e2e.sh
101+
- name: Run non-docker e2e tests
102+
if: matrix.platform != 'ubuntu-latest'
103+
run: |
104+
NODOCKER=true tests/e2e.sh

tests/e2e.bats

Lines changed: 0 additions & 56 deletions
This file was deleted.

tests/e2e.sh

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
#!/usr/bin/env bash
2+
# Copyright 2019 Google LLC
3+
#
4+
# Licensed under the Apache License, Version 2.0 (the "License");
5+
# you may not use this file except in compliance with the License.
6+
# You may obtain a copy of the License at
7+
#
8+
# http://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
15+
16+
set -euo pipefail
17+
18+
REPO="$(cd "$(dirname "${BASH_SOURCE[0]}")"/.. >/dev/null 2>&1 && pwd)"
19+
DIST=${REPO}/ts/demo-functions/dist
20+
TAG=${TAG:-dev}
21+
NODOCKER=${NODOCKER:-}
22+
EMPTY_OUTPUT=$(
23+
cat <<-EOF
24+
apiVersion: v1
25+
kind: List
26+
items: []
27+
EOF
28+
)
29+
30+
############################
31+
# Test framework
32+
############################
33+
34+
function testcase() {
35+
echo "testcase: ${1}"
36+
tmp=$(mktemp -d "/tmp/e2e.${1}.XXXXXXXX")
37+
cp -r "${REPO}"/example-configs/* "${tmp}"
38+
cd "${tmp}"
39+
}
40+
41+
function fail() {
42+
echo "FAIL: " "$@"
43+
exit 1
44+
}
45+
46+
function assert_empty_list() {
47+
content="$(<$1)"
48+
[[ ${content} = "${EMPTY_OUTPUT}" ]] || fail "Not empty list: ${content}"
49+
}
50+
51+
function assert_empty_string() {
52+
content="$(<$1)"
53+
[[ -z ${content} ]] || fail "Not empty list: ${content}"
54+
}
55+
56+
function assert_dir_exists() {
57+
[[ -d $1 ]] || fail "Dir not exist: $1"
58+
}
59+
60+
############################
61+
# Node Tests
62+
############################
63+
64+
testcase "node_no_op_stdout"
65+
node ${DIST}/no_op_run.js -i /dev/null >out.yaml
66+
assert_empty_list out.yaml
67+
68+
testcase "node_no_op_regular_files"
69+
echo "$EMPTY_OUTPUT" >in.yaml
70+
node ${DIST}/no_op_run.js -i in.yaml -o out.yaml
71+
assert_empty_list out.yaml
72+
73+
testcase "node_no_op_pipe"
74+
node ${DIST}/no_op_run.js -i /dev/null |
75+
node ${DIST}/no_op_run.js >out.yaml
76+
assert_empty_list out.yaml
77+
78+
testcase "node_no_op_devnull"
79+
node ${DIST}/no_op_run.js -i /dev/null |
80+
node ${DIST}/no_op_run.js |
81+
node ${DIST}/no_op_run.js -o /dev/null >out.yaml
82+
assert_empty_string out.yaml
83+
84+
testcase "node_label_namespace"
85+
node ${DIST}/read_yaml_run.js -i /dev/null -d source_dir=$(pwd) |
86+
node ${DIST}/label_namespace_run.js -d label_name=color -d label_value=orange |
87+
grep -q 'color: orange'
88+
89+
testcase "node_demo_func"
90+
node ${DIST}/read_yaml_run.js -i /dev/null -d source_dir=$(pwd) |
91+
node ${DIST}/mutate_psp_run.js |
92+
node ${DIST}/expand_team_cr_run.js |
93+
node ${DIST}/validate_rolebinding_run.js -d [email protected] |
94+
node ${DIST}/write_yaml_run.js -o /dev/null -d sink_dir=$(pwd) -d overwrite=true
95+
assert_dir_exists payments-dev
96+
assert_dir_exists payments-prod
97+
grep -q allowPrivilegeEscalation podsecuritypolicy_psp.yaml
98+
99+
############################
100+
# Docker Tests
101+
############################
102+
103+
[[ -z ${NODOCKER} ]] || { echo "Skipping docker tests"; exit 0; }
104+
105+
testcase "docker_no_op_stdout"
106+
docker run -i gcr.io/kpt-functions/no-op:${TAG} -i /dev/null >out.yaml
107+
assert_empty_list out.yaml
108+
109+
testcase "docker_no_op_regular_files"
110+
echo "$EMPTY_OUTPUT" >in.yaml
111+
docker run -i -u $(id -u) -v $(pwd):/source gcr.io/kpt-functions/no-op:${TAG} -i /source/in.yaml -o /source/out.yaml
112+
assert_empty_list out.yaml
113+
114+
testcase "docker_no_op_pipe"
115+
docker run -i gcr.io/kpt-functions/no-op:${TAG} -i /dev/null |
116+
docker run -i gcr.io/kpt-functions/no-op:${TAG} >out.yaml
117+
assert_empty_list out.yaml
118+
119+
testcase "docker_no_op_devnull"
120+
docker run -i gcr.io/kpt-functions/no-op:${TAG} -i /dev/null |
121+
docker run -i gcr.io/kpt-functions/no-op:${TAG} |
122+
docker run -i gcr.io/kpt-functions/no-op:${TAG} -o /dev/null >out.yaml
123+
assert_empty_string out.yaml
124+
125+
testcase "docker_label_namespace"
126+
docker run -i -u $(id -u) -v $(pwd):/source gcr.io/kpt-functions/read-yaml:${TAG} -i /dev/null -d source_dir=/source |
127+
docker run -i gcr.io/kpt-functions/label-namespace:${TAG} -d label_name=color -d label_value=orange |
128+
grep -q 'color: orange'
129+
130+
testcase "docker_demo_func"
131+
docker run -i -u $(id -u) -v $(pwd):/source gcr.io/kpt-functions/read-yaml:${TAG} -i /dev/null -d source_dir=/source |
132+
docker run -i gcr.io/kpt-functions/mutate-psp:${TAG} |
133+
docker run -i gcr.io/kpt-functions/expand-team-cr:${TAG} |
134+
docker run -i gcr.io/kpt-functions/validate-rolebinding:${TAG} -d [email protected] |
135+
docker run -i -u $(id -u) -v $(pwd):/sink gcr.io/kpt-functions/write-yaml:${TAG} -o /dev/null -d sink_dir=/sink -d overwrite=true
136+
assert_dir_exists payments-dev
137+
assert_dir_exists payments-prod
138+
grep -q allowPrivilegeEscalation podsecuritypolicy_psp.yaml

ts/kpt-functions/@types/rw/index.d.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@
1414
* limitations under the License.
1515
*/
1616

17-
// Package rw does not have type definitions, so we must define the ones we need here.
18-
19-
export function readFileSync(filename: string, options: string): string;
20-
21-
export function writeFileSync(filename: string, data: string, options: string): void;
17+
// Package rw does not have type definitions.
18+
declare module 'rw' {
19+
var rw: any;
20+
export default rw;
21+
}

ts/kpt-functions/src/io.ts

Lines changed: 32 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -14,18 +14,15 @@
1414
* limitations under the License.
1515
*/
1616

17-
import * as fs from 'fs';
1817
import { DumpOptions, safeDump, safeLoad } from 'js-yaml';
19-
import * as rw from 'rw';
18+
import rw from 'rw';
2019
import { Configs, KubernetesObject } from './types';
2120

2221
export enum FileFormat {
2322
YAML,
2423
JSON,
2524
}
26-
2725
type FilePath = string;
28-
2926
const YAML_STYLE: DumpOptions = {
3027
// indentation width to use (in spaces).
3128
indent: 2,
@@ -45,39 +42,24 @@ const YAML_STYLE: DumpOptions = {
4542
* @param format File format
4643
* @param functionConfig Either a path to the functionConfig file containing the object or the object itself.
4744
*/
48-
export function readConfigs(
45+
export async function readConfigs(
4946
input: FilePath,
5047
format: FileFormat,
5148
functionConfig?: FilePath | KubernetesObject
52-
): Configs {
49+
): Promise<Configs> {
5350
let inputRaw: string;
54-
55-
switch (input) {
56-
case '/dev/null':
57-
inputRaw = '{"items":[]}';
58-
break;
59-
case '/dev/stdin':
60-
if (process.stdin.isTTY) {
61-
throw new Error('Cannot read input. Need either stdin or --input file');
62-
}
63-
if (!fs.existsSync(input)) {
64-
throw new Error(`Input file does not exist: ${input}`);
65-
}
66-
inputRaw = rw.readFileSync(input, 'utf8');
67-
break;
68-
default:
69-
if (!fs.existsSync(input)) {
70-
throw new Error(`Input file does not exist: ${input}`);
71-
}
72-
inputRaw = rw.readFileSync(input, 'utf8');
51+
if (input === '/dev/null') {
52+
inputRaw = '{"items":[]}';
53+
} else {
54+
if (input === '/dev/stdin' && process.stdin.isTTY) {
55+
throw new Error('Cannot read input. Need either stdin or --input file');
56+
}
57+
inputRaw = await readFile(input);
7358
}
7459

7560
let functionConfigRaw: string | KubernetesObject | undefined;
7661
if (typeof functionConfig === 'string') {
77-
if (!fs.existsSync(functionConfig)) {
78-
throw new Error(`functionConfig file does not exist: ${functionConfig}`);
79-
}
80-
functionConfigRaw = rw.readFileSync(functionConfig, 'utf8');
62+
functionConfigRaw = await readFile(functionConfig);
8163
} else {
8264
functionConfigRaw = functionConfig;
8365
}
@@ -128,16 +110,16 @@ function load(raw: string, format: FileFormat): any {
128110
* @param configs Contains objects to write to the output file.
129111
* @param format defines whether to write the Configs as YAML or JSON.
130112
*/
131-
export function writeConfigs(
113+
export async function writeConfigs(
132114
output: FilePath,
133115
configs: Configs,
134116
format: FileFormat
135-
): void {
117+
): Promise<void> {
136118
if (output === '/dev/null') {
137119
return;
138120
}
139121

140-
rw.writeFileSync(output, stringify(configs, format), 'utf8');
122+
await writeFile(output, stringify(configs, format));
141123
}
142124

143125
/**
@@ -162,3 +144,21 @@ export function stringify(configs: Configs, format: FileFormat): string {
162144
throw new Error(`Unsupported file format ${format}`);
163145
}
164146
}
147+
148+
async function readFile(path: string): Promise<string> {
149+
return new Promise((resolve, reject) => {
150+
rw.readFile(path, 'utf8', (err: any, data: string) => {
151+
if (err) return reject(err);
152+
resolve(data);
153+
});
154+
});
155+
}
156+
157+
async function writeFile(path: string, data: string): Promise<void> {
158+
return new Promise((resolve, reject) => {
159+
rw.writeFile(path, data, 'utf8', (err: any) => {
160+
if (err) return reject(err);
161+
resolve();
162+
});
163+
});
164+
}

ts/kpt-functions/src/run.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -134,13 +134,13 @@ Use this ONLY if the function accepts a ConfigMap.`,
134134
}
135135

136136
// Read the input and construct Configs.
137-
const configs = readConfigs(inputFile, fileFormat, functionConfig);
137+
const configs = await readConfigs(inputFile, fileFormat, functionConfig);
138138

139139
// Run the function.
140140
await fn(configs);
141141

142142
// Write the output.
143-
writeConfigs(outputFile, configs, fileFormat);
143+
await writeConfigs(outputFile, configs, fileFormat);
144144
}
145145

146146
function parseToConfigMap(

0 commit comments

Comments
 (0)