Skip to content

Commit

Permalink
Merge pull request #385 from norbertklawikowski/run-examples-as-githu…
Browse files Browse the repository at this point in the history
…b-actions

Run examples as GitHub actions, fixes issue #379
  • Loading branch information
norbertklawikowski authored Jun 15, 2022
2 parents 8560aa0 + 7a0ac69 commit 40cf0d4
Show file tree
Hide file tree
Showing 4 changed files with 286 additions and 0 deletions.
54 changes: 54 additions & 0 deletions .github/actions/run-application/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
name: 'run-application'
description: 'Run an application, wait until an expected output appears, then optionally terminate the application'
inputs:
file:
description: 'Binary to run (file name only)'
required: true
args:
description: 'Parameters for the started binary'
required: false
default: ''
wait-for:
description: 'JSON string or array of strings to search for in the stdout/stderr output of the started application'
required: true
working-directory:
description: 'The directory where to run the application'
required: true
terminate:
description: 'When set to true, the started process will be terminated'
required: false
default: 'true'
outputs:
pid:
description: 'PID of started process, only relevant when terminate is set to true'
value: ${{ steps.runapp.outputs.pid }}
runs:
using: 'composite'
steps:
- name: 'Start application in background and save the PID'
id: runapp
shell: bash
working-directory: '${{ inputs.working-directory }}'
run: |
./${{ inputs.file }} ${{ inputs.args }} &> ${{ inputs.file }}.log & PID=$!
echo "::set-output name=pid::${PID}"
- name: 'Wait for output to appear'
shell: bash
working-directory: '${{ inputs.working-directory }}'
run: |
python3 ${GITHUB_WORKSPACE}/examples/wait_for_output.py ${{ inputs.file }}.log '${{ inputs.wait-for }}'
- name: 'Display log file'
shell: bash
working-directory: '${{ inputs.working-directory }}'
run: |
cat ${{ inputs.file }}.log
if: always()

- name: 'Send SIGTERM to process with pid ${{ steps.runapp.outputs.pid }}'
shell: bash
working-directory: '${{ inputs.working-directory }}'
run: |
kill ${{ steps.runapp.outputs.pid }}
if: ${{ inputs.terminate == 'true' }}
176 changes: 176 additions & 0 deletions .github/workflows/runexamples.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
name: run examples

on:
push:
branches: [master]
pull_request:
branches: [master]

jobs:
run-examples:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2

- name: Set up Go
uses: actions/setup-go@v2
with:
go-version: 1.16

- name: Build examples
shell: bash
working-directory: examples
run: |
go build -o 1-simplest/example-1 1-simplest/main.go
go build -o 2-clicks/example-2 2-clicks/main.go
go build -o 3-messaging/example-3-processor 3-messaging/cmd/processor/main.go
go build -o 3-messaging/example-3-service 3-messaging/cmd/service/main.go
go build -o 3-messaging/example-3-loadgen 3-messaging/cmd/loadgen/main.go
go build -o 3-messaging/example-3-block-user 3-messaging/cmd/block-user/main.go
go build -o 3-messaging/example-3-translate-word 3-messaging/cmd/translate-word/main.go
go build -o 5-multiple/example-5 5-multiple/main.go
go build -o 6-reconnecting-view/example-6 6-reconnecting-view/main.go
go build -o 7-redis/example-7 7-redis/*.go
go build -o 8-monitoring/example-8 8-monitoring/main.go
go build -o 9-defer-commit/example-9 9-defer-commit/main.go
go build -o 10-visit/example-10 10-visit/main.go
- name: Start services
shell: bash
working-directory: examples
run: |
make start
- name: Run example 1
uses: ./.github/actions/run-application
with:
file: example-1
wait-for: '"Processor example-group"'
working-directory: examples/1-simplest

- name: Run example 2
uses: ./.github/actions/run-application
with:
file: example-2
wait-for: '"Processor mini-group"'
working-directory: examples/2-clicks

# Example 3 is a more complex example consisting of multiple steps
- name: Start example 3 processors
id: run_example_3_processor
uses: ./.github/actions/run-application
with:
file: example-3-processor
args: -collector -blocker -filter -translator -detector
wait-for: '["Processor blocker", "Processor collector", "Processor message_filter", "Processor message_filter", "Processor message_filter"]'
working-directory: examples/3-messaging
terminate: false

- name: Start example 3 service
id: run_example_3_service
uses: ./.github/actions/run-application
with:
file: example-3-service
args: -sent
wait-for: '"Listen port 8080"'
working-directory: examples/3-messaging
terminate: false

- name: Start example 3 loadgen
id: run_example_3_loadgen
uses: ./.github/actions/run-application
with:
file: example-3-loadgen
wait-for: '"Posted message"'
working-directory: examples/3-messaging
terminate: false

- name: Run example 3 list users
shell: bash
working-directory: examples/3-messaging
run: |
curl localhost:8080/Alice/feed
- name: Run example 3 block user
shell: bash
working-directory: examples/3-messaging
run: |
./example-3-block-user -user Bob
- name: Run example 3 translate word
shell: bash
working-directory: examples/3-messaging
run: |
./example-3-translate-word -word "together" -with "t°9e+her"
- name: Stop example 3 loadgen
shell: bash
working-directory: examples/3-messaging
run: |
kill ${{ steps.run_example_3_loadgen.outputs.pid }}
- name: Stop example 3 service
shell: bash
working-directory: examples/3-messaging
run: |
kill ${{ steps.run_example_3_service.outputs.pid }}
- name: Stop example 3 processors
shell: bash
working-directory: examples/3-messaging
run: |
kill ${{ steps.run_example_3_processor.outputs.pid }}
- name: Run example 4
shell: bash
working-directory: examples/4-tests
run: |
go test example_test.go
- name: Run example 5
uses: ./.github/actions/run-application
with:
file: example-5
wait-for: '"Processor multiInput"'
working-directory: examples/5-multiple

- name: Run example 6
uses: ./.github/actions/run-application
with:
file: example-6
wait-for: '"View is in state"'
working-directory: examples/6-reconnecting-view

- name: Run example 7
uses: ./.github/actions/run-application
with:
file: example-7
wait-for: '"Processor examples"'
working-directory: examples/7-redis

- name: Run example 8
uses: ./.github/actions/run-application
with:
file: example-8
wait-for: '["Processor mini-group-stateless", "Processor mini-group-join", "Processor mini-group"]'
working-directory: examples/8-monitoring

- name: Run example 9
uses: ./.github/actions/run-application
with:
file: example-9
wait-for: '["Processor forwarder", "Processor consumer"]'
working-directory: examples/9-defer-commit

- name: Run example 10
uses: ./.github/actions/run-application
with:
file: example-10
wait-for: '"Processor example-visit-group"'
working-directory: examples/10-visit

- name: Stop services
shell: bash
working-directory: examples
run: |
make stop
1 change: 1 addition & 0 deletions examples/3-messaging/cmd/loadgen/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ func send(from, to, content string) {
log.Printf("error sending request: %v", err)
return
}
log.Printf("Posted message '%s' (%s -> %s)", content, from, to)
defer resp.Body.Close()
//TODO(diogo) check response status code
}
Expand Down
55 changes: 55 additions & 0 deletions examples/wait_for_output.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
""" Reads a file line by line repeatedly and searches the first occurrence of one or
more strings. When all strings have been found, the script terminates with return code 0,
if TIMEOUT_AFTER_SECONDS seconds have passed it will terminate with return code 1.
Only used in GitHub action 'run-application'.
"""

import sys
import json
import time
import argparse

TIMEOUT_AFTER_SECONDS = 10


def wait_until_output(file_to_scan, strings_to_match):
"""Reads a file line by line repeatedly and searches the first occurrence of one or
more strings, returns True if all have been found in less than TIMEOUT_AFTER_SECONDS,
else False.
"""
strings_to_match = set(strings_to_match)

for _ in range(TIMEOUT_AFTER_SECONDS):
with open(file_to_scan) as f:
for line in f:
for curr_str in list(strings_to_match):
if curr_str in line:
strings_to_match.remove(curr_str)
if not strings_to_match:
return True
time.sleep(1)

# not all strings found, return error
return False


def main():
parser = argparse.ArgumentParser()
parser.add_argument('file', help='path to file to read')
parser.add_argument('searchstrings', help='a JSON string or array of strings')
args = parser.parse_args()

file_to_scan = args.file
strings_to_match = json.loads(args.searchstrings)
if not isinstance(strings_to_match, list):
strings_to_match = [strings_to_match]

ret = wait_until_output(file_to_scan, strings_to_match)

if not ret:
sys.exit(1)


if __name__ == '__main__':
main()

0 comments on commit 40cf0d4

Please sign in to comment.