From f5ff03d3461dc7d24dba700c53102d0e23ecb843 Mon Sep 17 00:00:00 2001 From: debugtalk Date: Mon, 25 Apr 2022 16:23:38 +0800 Subject: [PATCH 001/109] refactor: get hrp version from aliyun OSS file --- .github/workflows/hrp-release.yml | 3 ++- README.en.md | 2 -- README.md | 2 -- docs/CHANGELOG.md | 1 + hrp/internal/version/VERSION | 1 + hrp/internal/version/init.go | 7 ++++++- httprunner/__init__.py | 2 +- pyproject.toml | 2 +- scripts/bump_version.sh | 9 +++++---- scripts/install.sh | 15 +++++++++------ 10 files changed, 26 insertions(+), 18 deletions(-) create mode 100644 hrp/internal/version/VERSION diff --git a/.github/workflows/hrp-release.yml b/.github/workflows/hrp-release.yml index 4dbb86bc..1821ccbc 100644 --- a/.github/workflows/hrp-release.yml +++ b/.github/workflows/hrp-release.yml @@ -42,7 +42,8 @@ jobs: access-key-secret: ${{ secrets.ALIYUN_ACCESSKEY_SECRET }} - name: Upload artifacts to aliyun OSS run: | + ossutil cp -rf hrp/internal/version/VERSION oss://httprunner/ ossutil cp -rf scripts/install.sh oss://httprunner/ ossutil cp -rf ${{ env.ASSET_PATH }} oss://httprunner/ - name: Test install.sh - run: bash -c "$(curl -ksSL https://httprunner.oss-cn-beijing.aliyuncs.com/install.sh)" + run: bash -c "$(curl -ksSL https://httprunner.com/script/install.sh)" diff --git a/README.en.md b/README.en.md index 511a02cd..d1a564e0 100644 --- a/README.en.md +++ b/README.en.md @@ -60,8 +60,6 @@ You can install HttpRunner via one curl command. ```bash $ bash -c "$(curl -ksSL https://httprunner.com/script/install.sh)" -# backup -$ bash -c "$(curl -ksSL https://httprunner.oss-cn-beijing.aliyuncs.com/install.sh)" ``` Then you will get a `hrp` CLI tool. diff --git a/README.md b/README.md index b701e953..5d7c2632 100644 --- a/README.md +++ b/README.md @@ -53,8 +53,6 @@ HttpRunner 二进制命令行工具已上传至阿里云 OSS,在系统终端 ```bash $ bash -c "$(curl -ksSL https://httprunner.com/script/install.sh)" -# backup -$ bash -c "$(curl -ksSL https://httprunner.oss-cn-beijing.aliyuncs.com/install.sh)" ``` 安装成功后,你将获得一个 `hrp` 命令行工具,执行 `hrp -h` 即可查看到参数帮助说明。 diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 54067d1d..4d026a8a 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -6,6 +6,7 @@ - feat: add builtin function `environ`/`ENV` - fix: demo function compatibility +- change: get hrp version from aliyun OSS file when installing ## v4.0.0-beta (2022-04-24) diff --git a/hrp/internal/version/VERSION b/hrp/internal/version/VERSION new file mode 100644 index 00000000..46390341 --- /dev/null +++ b/hrp/internal/version/VERSION @@ -0,0 +1 @@ +v4.0.0-beta \ No newline at end of file diff --git a/hrp/internal/version/init.go b/hrp/internal/version/init.go index ca433250..4887463b 100644 --- a/hrp/internal/version/init.go +++ b/hrp/internal/version/init.go @@ -1,3 +1,8 @@ package version -const VERSION = "v4.0.0-beta" +import ( + _ "embed" +) + +//go:embed VERSION +var VERSION string diff --git a/httprunner/__init__.py b/httprunner/__init__.py index 1329604f..cbc40932 100644 --- a/httprunner/__init__.py +++ b/httprunner/__init__.py @@ -1,4 +1,4 @@ -__version__ = "4.0.0-beta" +__version__ = "v4.0.0-beta" __description__ = "One-stop solution for HTTP(S) testing." from httprunner.config import Config diff --git a/pyproject.toml b/pyproject.toml index 9c098cf6..c50b5036 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "httprunner" -version = "4.0.0-beta" +version = "v4.0.0-beta" description = "One-stop solution for HTTP(S) testing." license = "Apache-2.0" readme = "README.md" diff --git a/scripts/bump_version.sh b/scripts/bump_version.sh index 84e730f6..1abba52d 100644 --- a/scripts/bump_version.sh +++ b/scripts/bump_version.sh @@ -16,11 +16,12 @@ if [ -z "$version" ]; then exit 1 fi -echo "bump hrp version to $version" -sed -i'.bak' "s/\".*\"/\"v$version\"/g" hrp/internal/version/init.go +if [[ $version != v* ]]; then + version="v$version" +fi -echo "bump install.sh version to $version" -sed -i'.bak' "s/LATEST_VERSION=\".*\"/LATEST_VERSION=\"v$version\"/g" scripts/install.sh +echo "bump hrp version to $version" +echo -n "$version" > hrp/internal/version/VERSION echo "bump httprunner version to $version" sed -i'.bak' "s/__version__ = \".*\"/__version__ = \"$version\"/g" httprunner/__init__.py diff --git a/scripts/install.sh b/scripts/install.sh index dfc11491..c16f1c05 100644 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -1,8 +1,6 @@ #!/bin/bash # install hrp with one shell command -# bash -c "$(curl -ksSL https://httprunner.oss-cn-beijing.aliyuncs.com/install.sh)" - -LATEST_VERSION="v4.0.0-beta" +# bash -c "$(curl -ksSL https://httprunner.com/script/install.sh)" set -e @@ -22,8 +20,7 @@ function echoWarn() { export -f echoError function get_latest_version() { - # Release v0.4.0 · httprunner/hrp · GitHub - curl -sL https://github.com/httprunner/httprunner/releases/latest | grep 'Release' | cut -d" " -f4 + curl -ksSL https://httprunner.oss-cn-beijing.aliyuncs.com/VERSION } function get_os() { @@ -61,7 +58,13 @@ function extract_pkg() { function main() { echoInfo "Detect target hrp package..." - version=$LATEST_VERSION + version=$(get_latest_version) + if [[ $version != v* ]]; then + echo "get hrp latest version failed:" + echo "$version" + exit 1 + fi + os=$(get_os) echo "Current OS: $os" arch=$(get_arch) From 1a1016bc378c80db0a81e38e36efd5c7dbe72af5 Mon Sep 17 00:00:00 2001 From: debugtalk <mail@debugtalk.com> Date: Mon, 25 Apr 2022 18:36:24 +0800 Subject: [PATCH 002/109] change: hrp install httprunner v4 --- go.mod | 2 +- go.sum | 4 ++-- hrp/internal/builtin/utils.go | 2 +- hrp/internal/convert/main.go | 4 +++- hrp/internal/pytest/main.go | 6 +++++- hrp/internal/scaffold/main.go | 2 +- hrp/internal/version/init.go | 2 ++ 7 files changed, 15 insertions(+), 7 deletions(-) diff --git a/go.mod b/go.mod index 49c7f16b..510515e6 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,7 @@ require ( github.com/getsentry/sentry-go v0.13.0 github.com/google/uuid v1.3.0 github.com/gorilla/websocket v1.4.1 - github.com/httprunner/funplugin v0.4.3 + github.com/httprunner/funplugin v0.4.4 github.com/jinzhu/copier v0.3.2 github.com/jmespath/go-jmespath v0.4.0 github.com/json-iterator/go v1.1.12 diff --git a/go.sum b/go.sum index 9647213c..24c26644 100644 --- a/go.sum +++ b/go.sum @@ -241,8 +241,8 @@ github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2p github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb h1:b5rjCoWHc7eqmAS4/qyk21ZsHyb6Mxv/jykxvNTkU4M= github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= -github.com/httprunner/funplugin v0.4.3 h1:mxdxQh54NZLQnK/FXZxpZV0rhqZQzckrWKEnBW5w2Vg= -github.com/httprunner/funplugin v0.4.3/go.mod h1:vPyeJIfbpGe0epZZtAV0wCn16gLY9+imSw/zfxq0Lcc= +github.com/httprunner/funplugin v0.4.4 h1:IVt603Y57WfSbn6DZ0R4iLeGQJ1yj944gmYwEOSBzGo= +github.com/httprunner/funplugin v0.4.4/go.mod h1:vPyeJIfbpGe0epZZtAV0wCn16gLY9+imSw/zfxq0Lcc= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/imkira/go-interpol v1.1.0/go.mod h1:z0h2/2T3XF8kyEPpRgJ3kmNv+C43p+I/CoI+jC3w2iA= diff --git a/hrp/internal/builtin/utils.go b/hrp/internal/builtin/utils.go index de2486a8..98c63c6f 100644 --- a/hrp/internal/builtin/utils.go +++ b/hrp/internal/builtin/utils.go @@ -86,7 +86,7 @@ func EnsurePython3Venv(packages ...string) (string, error) { venvDir := filepath.Join(home, ".hrp", "venv") python3, err := shared.EnsurePython3Venv(venvDir, packages...) if err != nil { - return "", errors.Wrap(err, "ensure python venv failed") + return "", errors.Wrap(err, "ensure python3 venv failed") } return python3, nil diff --git a/hrp/internal/convert/main.go b/hrp/internal/convert/main.go index a52a0fab..c871c55e 100644 --- a/hrp/internal/convert/main.go +++ b/hrp/internal/convert/main.go @@ -10,6 +10,7 @@ import ( "github.com/httprunner/httprunner/hrp" "github.com/httprunner/httprunner/hrp/internal/builtin" "github.com/httprunner/httprunner/hrp/internal/sdk" + "github.com/httprunner/httprunner/hrp/internal/version" ) func Convert2TestScripts(destType string, paths ...string) error { @@ -28,7 +29,8 @@ func Convert2TestScripts(destType string, paths ...string) error { } func convert2PyTestScripts(paths ...string) error { - python3, err := builtin.EnsurePython3Venv("httprunner") + httprunner := fmt.Sprintf("httprunner>=%s", version.HttpRunnerMinVersion) + python3, err := builtin.EnsurePython3Venv(httprunner) if err != nil { return err } diff --git a/hrp/internal/pytest/main.go b/hrp/internal/pytest/main.go index a70ead1e..7e749d4a 100644 --- a/hrp/internal/pytest/main.go +++ b/hrp/internal/pytest/main.go @@ -1,8 +1,11 @@ package pytest import ( + "fmt" + "github.com/httprunner/httprunner/hrp/internal/builtin" "github.com/httprunner/httprunner/hrp/internal/sdk" + "github.com/httprunner/httprunner/hrp/internal/version" ) func RunPytest(args []string) error { @@ -11,7 +14,8 @@ func RunPytest(args []string) error { Action: "hrp pytest", }) - python3, err := builtin.EnsurePython3Venv("httprunner") + httprunner := fmt.Sprintf("httprunner>=%s", version.HttpRunnerMinVersion) + python3, err := builtin.EnsurePython3Venv(httprunner) if err != nil { return err } diff --git a/hrp/internal/scaffold/main.go b/hrp/internal/scaffold/main.go index 92ff9ddf..98f5ffed 100644 --- a/hrp/internal/scaffold/main.go +++ b/hrp/internal/scaffold/main.go @@ -185,7 +185,7 @@ func createPythonPlugin(projectName string) error { return errors.Wrap(err, "copy file failed") } - _, err = builtin.EnsurePython3Venv(fmt.Sprintf("funppy==%s", shared.Version)) + _, err = builtin.EnsurePython3Venv(fmt.Sprintf("funppy>=%s", shared.Version)) if err != nil { return err } diff --git a/hrp/internal/version/init.go b/hrp/internal/version/init.go index 4887463b..65b56ebc 100644 --- a/hrp/internal/version/init.go +++ b/hrp/internal/version/init.go @@ -6,3 +6,5 @@ import ( //go:embed VERSION var VERSION string + +const HttpRunnerMinVersion = "v4.0.0-beta" From bc83c1c0dbd49068e952c1684a45902fc1ff3584 Mon Sep 17 00:00:00 2001 From: xucong053 <xucong053@outlook.com> Date: Thu, 28 Apr 2022 18:13:03 +0800 Subject: [PATCH 003/109] add reporting load testing metrics to prometheus --- hrp/internal/boomer/output.go | 97 +++++++++++++++++++++++++++++++++-- 1 file changed, 93 insertions(+), 4 deletions(-) diff --git a/hrp/internal/boomer/output.go b/hrp/internal/boomer/output.go index 8d1d125c..79284cf7 100644 --- a/hrp/internal/boomer/output.go +++ b/hrp/internal/boomer/output.go @@ -2,6 +2,7 @@ package boomer import ( "fmt" + "math" "os" "sort" "strconv" @@ -166,6 +167,7 @@ type statsEntryOutput struct { avgContentLength int64 // average content size currentRps float64 // # reqs/sec currentFailPerSec float64 // # fails/sec + duration float64 // the duration of stats } type dataOutput struct { @@ -175,8 +177,12 @@ type dataOutput struct { TransactionsPassed int64 `json:"transactions_passed"` TransactionsFailed int64 `json:"transactions_failed"` TotalAvgResponseTime float64 `json:"total_avg_response_time"` + TotalMinResponseTime float64 `json:"total_min_response_time"` + TotalMaxResponseTime float64 `json:"total_max_response_time"` TotalRPS float64 `json:"total_rps"` TotalFailRatio float64 `json:"total_fail_ratio"` + TotalFailPerSec float64 `json:"total_fail_per_sec"` + Duration float64 `json:"duration"` Stats []*statsEntryOutput `json:"stats"` Errors map[string]map[string]interface{} `json:"errors"` } @@ -217,12 +223,16 @@ func convertData(data map[string]interface{}) (output *dataOutput, err error) { output = &dataOutput{ UserCount: userCount, State: state, + Duration: entryTotalOutput.duration, TotalStats: entryTotalOutput, TransactionsPassed: transactionsPassed, TransactionsFailed: transactionsFailed, TotalAvgResponseTime: entryTotalOutput.avgResponseTime, + TotalMaxResponseTime: float64(entryTotalOutput.MaxResponseTime), + TotalMinResponseTime: float64(entryTotalOutput.MinResponseTime), TotalRPS: entryTotalOutput.currentRps, TotalFailRatio: getTotalFailRatio(entryTotalOutput.NumRequests, entryTotalOutput.NumFailures), + TotalFailPerSec: entryTotalOutput.currentFailPerSec, Stats: make([]*statsEntryOutput, 0, len(stats)), Errors: errors, } @@ -266,6 +276,7 @@ func deserializeStatsEntry(stat interface{}) (entryOutput *statsEntryOutput, err numRequests := entry.NumRequests entryOutput = &statsEntryOutput{ statsEntry: entry, + duration: duration, medianResponseTime: getMedianResponseTime(numRequests, entry.ResponseTimes), avgResponseTime: getAvgResponseTime(numRequests, entry.TotalResponseTime), avgContentLength: getAvgContentLength(numRequests, entry.TotalContentLength), @@ -351,6 +362,20 @@ var ( }, []string{"method", "name", "error"}, ) + counterTotalNumRequests = prometheus.NewGaugeVec( + prometheus.GaugeOpts{ + Name: "total_num_requests", + Help: "The number of requests in total", + }, + []string{"method", "name"}, + ) + counterTotalNumFailures = prometheus.NewGaugeVec( + prometheus.GaugeOpts{ + Name: "total_num_failures", + Help: "The number of failures in total", + }, + []string{"method", "name"}, + ) ) // summary for total @@ -360,9 +385,9 @@ var ( Name: "response_time", Help: "The summary of response time", Objectives: map[float64]float64{ - 0.5: 0.01, - 0.9: 0.01, - 0.95: 0.005, + 0.5: 0.01, // PCT50 + 0.9: 0.01, // PCT90 + 0.95: 0.005, // PCT95 }, AgeBuckets: 1, MaxAge: 100000 * time.Second, @@ -385,12 +410,32 @@ var ( Help: "The current runner state, 1=initializing, 2=spawning, 3=running, 4=quitting, 5=stopped", }, ) + gaugeDuration = prometheus.NewGauge( + prometheus.GaugeOpts{ + Name: "duration", + Help: "The duration of load testing", + }, + ) gaugeTotalAverageResponseTime = prometheus.NewGauge( prometheus.GaugeOpts{ Name: "total_average_response_time", Help: "The average response time in total milliseconds", }, ) + gaugeTotalMinResponseTime = prometheus.NewGaugeVec( + prometheus.GaugeOpts{ + Name: "total_min_response_time", + Help: "The min response time in total milliseconds", + }, + []string{"method", "name"}, + ) + gaugeTotalMaxResponseTime = prometheus.NewGaugeVec( + prometheus.GaugeOpts{ + Name: "total_max_response_time", + Help: "The max response time in total milliseconds", + }, + []string{"method", "name"}, + ) gaugeTotalRPS = prometheus.NewGauge( prometheus.GaugeOpts{ Name: "total_rps", @@ -403,6 +448,12 @@ var ( Help: "The ratio of request failures in total", }, ) + gaugeTotalFailPerSec = prometheus.NewGauge( + prometheus.GaugeOpts{ + Name: "total_fail_per_sec", + Help: "The failure number per second in total", + }, + ) gaugeTransactionsPassed = prometheus.NewGauge( prometheus.GaugeOpts{ Name: "transactions_passed", @@ -417,6 +468,11 @@ var ( ) ) +var ( + minResponseTimeMap = map[string]float64{} + maxResponseTimeMap = map[string]float64{} +) + // NewPrometheusPusherOutput returns a PrometheusPusherOutput. func NewPrometheusPusherOutput(gatewayURL, jobName string) *PrometheusPusherOutput { nodeUUID, _ := uuid.NewUUID() @@ -447,14 +503,20 @@ func (o *PrometheusPusherOutput) OnStart() { gaugeCurrentFailPerSec, // counter for total counterErrors, + counterTotalNumRequests, + counterTotalNumFailures, // summary for total summaryResponseTime, // gauges for total gaugeUsers, gaugeState, + gaugeDuration, gaugeTotalAverageResponseTime, + gaugeTotalMinResponseTime, + gaugeTotalMaxResponseTime, gaugeTotalRPS, gaugeTotalFailRatio, + gaugeTotalFailPerSec, gaugeTransactionsPassed, gaugeTransactionsFailed, ) @@ -484,8 +546,13 @@ func (o *PrometheusPusherOutput) OnEvent(data map[string]interface{}) { // runner state gaugeState.Set(float64(output.State)) - // avg response time in total + // min/avg/max response time in total gaugeTotalAverageResponseTime.Set(output.TotalAvgResponseTime) + gaugeTotalMinResponseTime.WithLabelValues("", "Total").Set(output.TotalMinResponseTime) + gaugeTotalMaxResponseTime.WithLabelValues("", "Total").Set(output.TotalMaxResponseTime) + + // duration + gaugeDuration.Set(output.Duration) // rps in total gaugeTotalRPS.Set(output.TotalRPS) @@ -493,6 +560,9 @@ func (o *PrometheusPusherOutput) OnEvent(data map[string]interface{}) { // failure ratio in total gaugeTotalFailRatio.Set(output.TotalFailRatio) + // failure per second in total + gaugeTotalFailPerSec.Set(output.TotalFailPerSec) + // accumulated number of transactions gaugeTransactionsPassed.Set(float64(output.TransactionsPassed)) gaugeTransactionsFailed.Set(float64(output.TransactionsFailed)) @@ -500,6 +570,7 @@ func (o *PrometheusPusherOutput) OnEvent(data map[string]interface{}) { for _, stat := range output.Stats { method := stat.Method name := stat.Name + // stats in stats interval gaugeNumRequests.WithLabelValues(method, name).Set(float64(stat.NumRequests)) gaugeNumFailures.WithLabelValues(method, name).Set(float64(stat.NumFailures)) gaugeMedianResponseTime.WithLabelValues(method, name).Set(float64(stat.medianResponseTime)) @@ -515,6 +586,24 @@ func (o *PrometheusPusherOutput) OnEvent(data map[string]interface{}) { summaryResponseTime.WithLabelValues(method, name).Observe(float64(responseTime)) } } + // every stat in total + key := fmt.Sprintf("%v_%v", method, name) + if _, ok := minResponseTimeMap[key]; !ok { + minResponseTimeMap[key] = float64(stat.MinResponseTime) + } else { + minResponseTimeMap[key] = math.Min(float64(stat.MinResponseTime), minResponseTimeMap[key]) + } + gaugeTotalMinResponseTime.WithLabelValues(method, name).Set(minResponseTimeMap[key]) + + if _, ok := maxResponseTimeMap[key]; !ok { + maxResponseTimeMap[key] = float64(stat.MaxResponseTime) + } else { + maxResponseTimeMap[key] = math.Max(float64(stat.MaxResponseTime), maxResponseTimeMap[key]) + } + gaugeTotalMaxResponseTime.WithLabelValues(method, name).Set(maxResponseTimeMap[key]) + + counterTotalNumRequests.WithLabelValues(method, name).Add(float64(stat.NumRequests)) + counterTotalNumFailures.WithLabelValues(method, name).Add(float64(stat.NumFailures)) } // errors From a3a2f2b531202ec11e87b16861927cf85e0108d4 Mon Sep 17 00:00:00 2001 From: xucong053 <xucong053@outlook.com> Date: Fri, 29 Apr 2022 14:19:44 +0800 Subject: [PATCH 004/109] fix: unsafe to write concurrent map and failed to parse parameters(type:[][]interface{}) in testcase script while data driven for testing --- hrp/boomer.go | 2 +- hrp/parameters.go | 4 ++++ hrp/parameters_test.go | 14 ++++++++++++++ hrp/session.go | 14 +++++++------- 4 files changed, 26 insertions(+), 8 deletions(-) diff --git a/hrp/boomer.go b/hrp/boomer.go index 271f00d7..377e61f3 100644 --- a/hrp/boomer.go +++ b/hrp/boomer.go @@ -108,7 +108,7 @@ func (b *HRPBoomer) convertBoomerTask(testcase *TestCase, rendezvousList []*Rend sessionRunner := caseRunner.newSession() if parametersIterator.HasNext() { - sessionRunner.updateConfigVariables(parametersIterator.Next()) + sessionRunner.updateSessionVariables(parametersIterator.Next()) } startTime := time.Now() diff --git a/hrp/parameters.go b/hrp/parameters.go index 33631c2d..5797f618 100644 --- a/hrp/parameters.go +++ b/hrp/parameters.go @@ -311,6 +311,10 @@ func convertParameters(key string, parametersRawList interface{}) (parameterSlic for i := 0; i < parametersRawSlice.Len(); i++ { parametersLine := make(map[string]interface{}) elem := parametersRawSlice.Index(i) + // e.g. Type: interface{} | []interface{}, convert interface{} to []interface{} + if elem.Kind() == reflect.Interface { + elem = elem.Elem() + } switch elem.Kind() { case reflect.Slice: // case 3 diff --git a/hrp/parameters_test.go b/hrp/parameters_test.go index 0276be91..19bb9fae 100644 --- a/hrp/parameters_test.go +++ b/hrp/parameters_test.go @@ -47,6 +47,20 @@ func TestLoadParameters(t *testing.T) { }, }, }, + { + map[string]interface{}{ + "username-password": []interface{}{ + []interface{}{"test1", "111111"}, + []interface{}{"test2", "222222"}, + }, + }, + map[string]Parameters{ + "username-password": { + {"username": "test1", "password": "111111"}, + {"username": "test2", "password": "222222"}, + }, + }, + }, { map[string]interface{}{}, nil, diff --git a/hrp/session.go b/hrp/session.go index ba643f1e..96710284 100644 --- a/hrp/session.go +++ b/hrp/session.go @@ -52,12 +52,12 @@ func (r *SessionRunner) Start(givenVars map[string]interface{}) error { config := r.testCase.Config log.Info().Str("testcase", config.Name).Msg("run testcase start") - // update config variables with given variables - r.updateConfigVariables(givenVars) - // reset session runner r.resetSession() + // update config variables with given variables + r.updateSessionVariables(givenVars) + // run step in sequential order for _, step := range r.testCase.TestSteps { log.Info().Str("step", step.Name()). @@ -122,16 +122,16 @@ func (r *SessionRunner) MergeStepVariables(vars map[string]interface{}) (map[str return parsedVariables, nil } -// updateConfigVariables updates config variables with given variables. +// updateSessionVariables updates session variables with given variables. // this is used for data driven -func (r *SessionRunner) updateConfigVariables(parameters map[string]interface{}) { +func (r *SessionRunner) updateSessionVariables(parameters map[string]interface{}) { if len(parameters) == 0 { return } - log.Info().Interface("parameters", parameters).Msg("update config variables") + log.Info().Interface("parameters", parameters).Msg("update session variables") for k, v := range parameters { - r.parsedConfig.Variables[k] = v + r.sessionVariables[k] = v } } From 061d2672462dd458f2322cc73d54346a0d26be93 Mon Sep 17 00:00:00 2001 From: debugtalk <mail@debugtalk.com> Date: Tue, 3 May 2022 10:46:05 +0800 Subject: [PATCH 005/109] fix #1240: losing host port in har2case --- docs/CHANGELOG.md | 1 + hrp/internal/har2case/core.go | 2 +- hrp/internal/har2case/core_test.go | 17 +++++++++++++++++ 3 files changed, 19 insertions(+), 1 deletion(-) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 4d026a8a..17ab8c0d 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -6,6 +6,7 @@ - feat: add builtin function `environ`/`ENV` - fix: demo function compatibility +- fix #1240: losing host port in har2case - change: get hrp version from aliyun OSS file when installing ## v4.0.0-beta (2022-04-24) diff --git a/hrp/internal/har2case/core.go b/hrp/internal/har2case/core.go index 5852ee4c..d3772dd2 100644 --- a/hrp/internal/har2case/core.go +++ b/hrp/internal/har2case/core.go @@ -187,7 +187,7 @@ func (s *tStep) makeRequestURL(entry *Entry) error { log.Error().Err(err).Msg("make request url failed") return err } - s.Request.URL = fmt.Sprintf("%s://%s", u.Scheme, u.Hostname()+u.Path) + s.Request.URL = fmt.Sprintf("%s://%s", u.Scheme, u.Host+u.Path) return nil } diff --git a/hrp/internal/har2case/core_test.go b/hrp/internal/har2case/core_test.go index fae4a3f5..25779e12 100644 --- a/hrp/internal/har2case/core_test.go +++ b/hrp/internal/har2case/core_test.go @@ -142,6 +142,23 @@ func TestGetFilenameWithoutExtension(t *testing.T) { } } +func TestMakeRequestURL(t *testing.T) { + har := NewHAR("") + entry := &Entry{ + Request: Request{ + URL: "http://127.0.0.1:8080/api/login", + }, + } + step, err := har.prepareTestStep(entry) + if !assert.NoError(t, err) { + t.Fatal() + } + + if !assert.Equal(t, "http://127.0.0.1:8080/api/login", step.Request.URL) { + t.Fatal() + } +} + func TestMakeRequestHeaders(t *testing.T) { har := NewHAR("") entry := &Entry{ From 2834f2950459ae6d271090f70f2cc071615115e9 Mon Sep 17 00:00:00 2001 From: debugtalk <mail@debugtalk.com> Date: Tue, 3 May 2022 11:02:17 +0800 Subject: [PATCH 006/109] bump version to v4.0.0 --- docs/CHANGELOG.md | 4 +- docs/cmd/hrp.md | 2 +- docs/cmd/hrp_boom.md | 2 +- docs/cmd/hrp_convert.md | 2 +- docs/cmd/hrp_har2case.md | 2 +- docs/cmd/hrp_pytest.md | 2 +- docs/cmd/hrp_run.md | 2 +- docs/cmd/hrp_startproject.md | 2 +- examples/data/a_b_c/T1_test.py | 2 +- examples/data/a_b_c/T2_3_test.py | 2 +- .../demo-with-go-plugin/plugin/debugtalk.go | 4 +- .../testcases/demo_ref_testcase.yml | 2 +- .../testcases/demo_requests.yml | 6 +- examples/demo-with-py-plugin/debugtalk.py | 6 +- .../demo-with-py-plugin/testcases/__init__.py | 1 - .../testcases/demo_ref_testcase.yml | 2 +- .../testcases/demo_ref_testcase_test.py | 60 -------------- .../testcases/demo_requests.yml | 6 +- .../testcases/demo_requests_test.py | 83 ------------------- examples/httpbin/basic_test.py | 2 +- examples/httpbin/hooks_test.py | 2 +- examples/httpbin/load_image_test.py | 2 +- examples/httpbin/upload_test.py | 2 +- examples/httpbin/validate_test.py | 2 +- .../cookie_manipulation/hardcode_test.py | 2 +- .../set_delete_cookies_test.py | 2 +- .../request_methods/hardcode_test.py | 2 +- .../request_with_functions_test.py | 2 +- .../request_with_parameters_test.py | 2 +- .../request_with_testcase_reference_test.py | 2 +- .../request_with_variables_test.py | 2 +- .../validate_with_functions_test.py | 2 +- .../validate_with_variables_test.py | 2 +- .../scaffold/templates/plugin/debugtalk.go | 4 +- .../scaffold/templates/plugin/debugtalk.py | 6 +- .../templates/testcases/demo_ref_testcase.yml | 2 +- .../testcases/demo_ref_testcase_test.py | 4 +- .../templates/testcases/demo_requests.yml | 6 +- .../templates/testcases/demo_requests_test.py | 8 +- hrp/internal/version/VERSION | 2 +- httprunner/__init__.py | 2 +- httprunner/make.py | 2 +- pyproject.toml | 2 +- 43 files changed, 58 insertions(+), 200 deletions(-) delete mode 100644 examples/demo-with-py-plugin/testcases/__init__.py delete mode 100644 examples/demo-with-py-plugin/testcases/demo_ref_testcase_test.py delete mode 100644 examples/demo-with-py-plugin/testcases/demo_requests_test.py diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 17ab8c0d..b6656601 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -1,13 +1,15 @@ # Release History -## v4.0.0-beta2 (2022-04-25) +## v4.0.0-beta2 (2022-05-03) **go version** - feat: add builtin function `environ`/`ENV` - fix: demo function compatibility - fix #1240: losing host port in har2case +- fix: concurrent map write in parameterize - change: get hrp version from aliyun OSS file when installing +- change: report more load testing metrics to prometheus ## v4.0.0-beta (2022-04-24) diff --git a/docs/cmd/hrp.md b/docs/cmd/hrp.md index 4a0c55d0..ef528f9d 100644 --- a/docs/cmd/hrp.md +++ b/docs/cmd/hrp.md @@ -36,4 +36,4 @@ Copyright 2017 debugtalk * [hrp run](hrp_run.md) - run API test with go engine * [hrp startproject](hrp_startproject.md) - create a scaffold project -###### Auto generated by spf13/cobra on 24-Apr-2022 +###### Auto generated by spf13/cobra on 3-May-2022 diff --git a/docs/cmd/hrp_boom.md b/docs/cmd/hrp_boom.md index ebc6bf4c..fe95ddab 100644 --- a/docs/cmd/hrp_boom.md +++ b/docs/cmd/hrp_boom.md @@ -41,4 +41,4 @@ hrp boom [flags] * [hrp](hrp.md) - Next-Generation API Testing Solution. -###### Auto generated by spf13/cobra on 24-Apr-2022 +###### Auto generated by spf13/cobra on 3-May-2022 diff --git a/docs/cmd/hrp_convert.md b/docs/cmd/hrp_convert.md index 6ec6b49e..880bce0b 100644 --- a/docs/cmd/hrp_convert.md +++ b/docs/cmd/hrp_convert.md @@ -18,4 +18,4 @@ hrp convert $path... [flags] * [hrp](hrp.md) - Next-Generation API Testing Solution. -###### Auto generated by spf13/cobra on 24-Apr-2022 +###### Auto generated by spf13/cobra on 3-May-2022 diff --git a/docs/cmd/hrp_har2case.md b/docs/cmd/hrp_har2case.md index 0d15ec54..d7b6e925 100644 --- a/docs/cmd/hrp_har2case.md +++ b/docs/cmd/hrp_har2case.md @@ -24,4 +24,4 @@ hrp har2case $har_path... [flags] * [hrp](hrp.md) - Next-Generation API Testing Solution. -###### Auto generated by spf13/cobra on 24-Apr-2022 +###### Auto generated by spf13/cobra on 3-May-2022 diff --git a/docs/cmd/hrp_pytest.md b/docs/cmd/hrp_pytest.md index bc0c2437..5c59d217 100644 --- a/docs/cmd/hrp_pytest.md +++ b/docs/cmd/hrp_pytest.md @@ -16,4 +16,4 @@ hrp pytest $path ... [flags] * [hrp](hrp.md) - Next-Generation API Testing Solution. -###### Auto generated by spf13/cobra on 24-Apr-2022 +###### Auto generated by spf13/cobra on 3-May-2022 diff --git a/docs/cmd/hrp_run.md b/docs/cmd/hrp_run.md index d7581b4c..6d2f8486 100644 --- a/docs/cmd/hrp_run.md +++ b/docs/cmd/hrp_run.md @@ -34,4 +34,4 @@ hrp run $path... [flags] * [hrp](hrp.md) - Next-Generation API Testing Solution. -###### Auto generated by spf13/cobra on 24-Apr-2022 +###### Auto generated by spf13/cobra on 3-May-2022 diff --git a/docs/cmd/hrp_startproject.md b/docs/cmd/hrp_startproject.md index 3402b1c9..a8706619 100644 --- a/docs/cmd/hrp_startproject.md +++ b/docs/cmd/hrp_startproject.md @@ -20,4 +20,4 @@ hrp startproject $project_name [flags] * [hrp](hrp.md) - Next-Generation API Testing Solution. -###### Auto generated by spf13/cobra on 24-Apr-2022 +###### Auto generated by spf13/cobra on 3-May-2022 diff --git a/examples/data/a_b_c/T1_test.py b/examples/data/a_b_c/T1_test.py index d3273df7..504b3f6d 100644 --- a/examples/data/a_b_c/T1_test.py +++ b/examples/data/a_b_c/T1_test.py @@ -1,4 +1,4 @@ -# NOTE: Generated By HttpRunner v4.0.0-beta +# NOTE: Generated By HttpRunner v4.0.0 # FROM: a-b.c/1.yml diff --git a/examples/data/a_b_c/T2_3_test.py b/examples/data/a_b_c/T2_3_test.py index a225b4cb..452927a9 100644 --- a/examples/data/a_b_c/T2_3_test.py +++ b/examples/data/a_b_c/T2_3_test.py @@ -1,4 +1,4 @@ -# NOTE: Generated By HttpRunner v4.0.0-beta +# NOTE: Generated By HttpRunner v4.0.0 # FROM: a-b.c/2 3.yml diff --git a/examples/demo-with-go-plugin/plugin/debugtalk.go b/examples/demo-with-go-plugin/plugin/debugtalk.go index dbb37554..b3b39400 100644 --- a/examples/demo-with-go-plugin/plugin/debugtalk.go +++ b/examples/demo-with-go-plugin/plugin/debugtalk.go @@ -42,11 +42,11 @@ func TeardownHookExample(args string) string { } func GetVersion() string { - return "v4.0.0-beta" + return fungo.Version } func main() { - fungo.Register("get_httprunner_version", GetVersion) + fungo.Register("get_version", GetVersion) fungo.Register("sum_ints", SumInts) fungo.Register("sum_two_int", SumTwoInt) fungo.Register("sum_two", SumTwoInt) diff --git a/examples/demo-with-go-plugin/testcases/demo_ref_testcase.yml b/examples/demo-with-go-plugin/testcases/demo_ref_testcase.yml index 7c9bcd19..0743488e 100644 --- a/examples/demo-with-go-plugin/testcases/demo_ref_testcase.yml +++ b/examples/demo-with-go-plugin/testcases/demo_ref_testcase.yml @@ -24,7 +24,7 @@ teststeps: method: POST url: /post headers: - User-Agent: HttpRunner/${get_httprunner_version()} + User-Agent: funplugin/${get_version()} Content-Type: "application/x-www-form-urlencoded" data: "foo1=$foo1&foo2=$foo3" validate: diff --git a/examples/demo-with-go-plugin/testcases/demo_requests.yml b/examples/demo-with-go-plugin/testcases/demo_requests.yml index cae1b491..86d1b9cc 100644 --- a/examples/demo-with-go-plugin/testcases/demo_requests.yml +++ b/examples/demo-with-go-plugin/testcases/demo_requests.yml @@ -24,7 +24,7 @@ teststeps: foo2: $foo2 sum_v: $sum_v headers: - User-Agent: HttpRunner/${get_httprunner_version()} + User-Agent: funplugin/${get_version()} extract: foo3: "body.args.foo2" validate: @@ -41,7 +41,7 @@ teststeps: method: POST url: /post headers: - User-Agent: HttpRunner/${get_httprunner_version()} + User-Agent: funplugin/${get_version()} Content-Type: "text/plain" data: "This is expected to be sent back as part of response body: $foo1-$foo2-$foo3." validate: @@ -55,7 +55,7 @@ teststeps: method: POST url: /post headers: - User-Agent: HttpRunner/${get_httprunner_version()} + User-Agent: funplugin/${get_version()} Content-Type: "application/x-www-form-urlencoded" data: "foo1=$foo1&foo2=$foo2&foo3=$foo3" validate: diff --git a/examples/demo-with-py-plugin/debugtalk.py b/examples/demo-with-py-plugin/debugtalk.py index 180725e0..9fd41120 100644 --- a/examples/demo-with-py-plugin/debugtalk.py +++ b/examples/demo-with-py-plugin/debugtalk.py @@ -5,8 +5,8 @@ from typing import List import funppy -def get_httprunner_version(): - return "v4.0.0-beta" +def get_version(): + return funppy.__version__ def sleep(n_secs): @@ -60,7 +60,7 @@ def teardown_hook_example(name): if __name__ == "__main__": - funppy.register("get_httprunner_version", get_httprunner_version) + funppy.register("get_version", get_version) funppy.register("sum", sum) funppy.register("sum_ints", sum_ints) funppy.register("concatenate", concatenate) diff --git a/examples/demo-with-py-plugin/testcases/__init__.py b/examples/demo-with-py-plugin/testcases/__init__.py deleted file mode 100644 index 70cfba53..00000000 --- a/examples/demo-with-py-plugin/testcases/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# NOTICE: Generated By HttpRunner. DO NOT EDIT! diff --git a/examples/demo-with-py-plugin/testcases/demo_ref_testcase.yml b/examples/demo-with-py-plugin/testcases/demo_ref_testcase.yml index 7c9bcd19..0743488e 100644 --- a/examples/demo-with-py-plugin/testcases/demo_ref_testcase.yml +++ b/examples/demo-with-py-plugin/testcases/demo_ref_testcase.yml @@ -24,7 +24,7 @@ teststeps: method: POST url: /post headers: - User-Agent: HttpRunner/${get_httprunner_version()} + User-Agent: funplugin/${get_version()} Content-Type: "application/x-www-form-urlencoded" data: "foo1=$foo1&foo2=$foo3" validate: diff --git a/examples/demo-with-py-plugin/testcases/demo_ref_testcase_test.py b/examples/demo-with-py-plugin/testcases/demo_ref_testcase_test.py deleted file mode 100644 index e88b8d08..00000000 --- a/examples/demo-with-py-plugin/testcases/demo_ref_testcase_test.py +++ /dev/null @@ -1,60 +0,0 @@ -# NOTE: Generated By HttpRunner v3.1.11 -# FROM: testcases/demo_ref_testcase.yml - - -import sys -from pathlib import Path - -sys.path.insert(0, str(Path(__file__).parent.parent)) - - -from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCase - -from testcases.demo_requests_test import TestCaseDemoRequests as DemoRequests - - -class TestCaseDemoRefTestcase(HttpRunner): - - config = ( - Config("request methods testcase: reference testcase") - .variables( - **{ - "foo1": "testsuite_config_bar1", - "expect_foo1": "testsuite_config_bar1", - "expect_foo2": "config_bar2", - } - ) - .base_url("https://postman-echo.com") - .verify(False) - ) - - teststeps = [ - Step( - RunTestCase("request with functions") - .with_variables( - **{"foo1": "testcase_ref_bar1", "expect_foo1": "testcase_ref_bar1"} - ) - .call(DemoRequests) - .export(*["foo3"]) - ), - Step( - RunRequest("post form data") - .with_variables(**{"foo1": "bar1"}) - .post("/post") - .with_headers( - **{ - "User-Agent": "HttpRunner/${get_httprunner_version()}", - "Content-Type": "application/x-www-form-urlencoded", - } - ) - .with_data("foo1=$foo1&foo2=$foo3") - .validate() - .assert_equal("status_code", 200) - .assert_equal("body.form.foo1", "bar1") - .assert_equal("body.form.foo2", "bar21") - ), - ] - - -if __name__ == "__main__": - TestCaseDemoRefTestcase().test_start() diff --git a/examples/demo-with-py-plugin/testcases/demo_requests.yml b/examples/demo-with-py-plugin/testcases/demo_requests.yml index cae1b491..86d1b9cc 100644 --- a/examples/demo-with-py-plugin/testcases/demo_requests.yml +++ b/examples/demo-with-py-plugin/testcases/demo_requests.yml @@ -24,7 +24,7 @@ teststeps: foo2: $foo2 sum_v: $sum_v headers: - User-Agent: HttpRunner/${get_httprunner_version()} + User-Agent: funplugin/${get_version()} extract: foo3: "body.args.foo2" validate: @@ -41,7 +41,7 @@ teststeps: method: POST url: /post headers: - User-Agent: HttpRunner/${get_httprunner_version()} + User-Agent: funplugin/${get_version()} Content-Type: "text/plain" data: "This is expected to be sent back as part of response body: $foo1-$foo2-$foo3." validate: @@ -55,7 +55,7 @@ teststeps: method: POST url: /post headers: - User-Agent: HttpRunner/${get_httprunner_version()} + User-Agent: funplugin/${get_version()} Content-Type: "application/x-www-form-urlencoded" data: "foo1=$foo1&foo2=$foo2&foo3=$foo3" validate: diff --git a/examples/demo-with-py-plugin/testcases/demo_requests_test.py b/examples/demo-with-py-plugin/testcases/demo_requests_test.py deleted file mode 100644 index b5c34bc2..00000000 --- a/examples/demo-with-py-plugin/testcases/demo_requests_test.py +++ /dev/null @@ -1,83 +0,0 @@ -# NOTE: Generated By HttpRunner v3.1.11 -# FROM: testcases/demo_requests.yml - - -from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCase - - -class TestCaseDemoRequests(HttpRunner): - - config = ( - Config("request methods testcase with functions") - .variables( - **{ - "foo1": "config_bar1", - "foo2": "config_bar2", - "expect_foo1": "config_bar1", - "expect_foo2": "config_bar2", - } - ) - .base_url("https://postman-echo.com") - .verify(False) - .export(*["foo3"]) - ) - - teststeps = [ - Step( - RunRequest("get with params") - .with_variables( - **{"foo1": "bar11", "foo2": "bar21", "sum_v": "${sum_two_int(1, 2)}"} - ) - .get("/get") - .with_params(**{"foo1": "$foo1", "foo2": "$foo2", "sum_v": "$sum_v"}) - .with_headers(**{"User-Agent": "HttpRunner/${get_httprunner_version()}"}) - .extract() - .with_jmespath("body.args.foo2", "foo3") - .validate() - .assert_equal("status_code", 200) - .assert_equal("body.args.foo1", "bar11") - .assert_equal("body.args.sum_v", "3") - .assert_equal("body.args.foo2", "bar21") - ), - Step( - RunRequest("post raw text") - .with_variables(**{"foo1": "bar12", "foo3": "bar32"}) - .post("/post") - .with_headers( - **{ - "User-Agent": "HttpRunner/${get_httprunner_version()}", - "Content-Type": "text/plain", - } - ) - .with_data( - "This is expected to be sent back as part of response body: $foo1-$foo2-$foo3." - ) - .validate() - .assert_equal("status_code", 200) - .assert_equal( - "body.data", - "This is expected to be sent back as part of response body: bar12-$expect_foo2-bar32.", - ) - ), - Step( - RunRequest("post form data") - .with_variables(**{"foo2": "bar23"}) - .post("/post") - .with_headers( - **{ - "User-Agent": "HttpRunner/${get_httprunner_version()}", - "Content-Type": "application/x-www-form-urlencoded", - } - ) - .with_data("foo1=$foo1&foo2=$foo2&foo3=$foo3") - .validate() - .assert_equal("status_code", 200) - .assert_equal("body.form.foo1", "$expect_foo1") - .assert_equal("body.form.foo2", "bar23") - .assert_equal("body.form.foo3", "bar21") - ), - ] - - -if __name__ == "__main__": - TestCaseDemoRequests().test_start() diff --git a/examples/httpbin/basic_test.py b/examples/httpbin/basic_test.py index d13c87ac..42733b08 100644 --- a/examples/httpbin/basic_test.py +++ b/examples/httpbin/basic_test.py @@ -1,4 +1,4 @@ -# NOTE: Generated By HttpRunner v4.0.0-beta +# NOTE: Generated By HttpRunner v4.0.0 # FROM: basic.yml diff --git a/examples/httpbin/hooks_test.py b/examples/httpbin/hooks_test.py index 96302d35..cb249adb 100644 --- a/examples/httpbin/hooks_test.py +++ b/examples/httpbin/hooks_test.py @@ -1,4 +1,4 @@ -# NOTE: Generated By HttpRunner v4.0.0-beta +# NOTE: Generated By HttpRunner v4.0.0 # FROM: hooks.yml diff --git a/examples/httpbin/load_image_test.py b/examples/httpbin/load_image_test.py index a43f5e75..9c56466c 100644 --- a/examples/httpbin/load_image_test.py +++ b/examples/httpbin/load_image_test.py @@ -1,4 +1,4 @@ -# NOTE: Generated By HttpRunner v4.0.0-beta +# NOTE: Generated By HttpRunner v4.0.0 # FROM: load_image.yml diff --git a/examples/httpbin/upload_test.py b/examples/httpbin/upload_test.py index 394ecbb6..56d2c1c8 100644 --- a/examples/httpbin/upload_test.py +++ b/examples/httpbin/upload_test.py @@ -1,4 +1,4 @@ -# NOTE: Generated By HttpRunner v4.0.0-beta +# NOTE: Generated By HttpRunner v4.0.0 # FROM: upload.yml diff --git a/examples/httpbin/validate_test.py b/examples/httpbin/validate_test.py index ae948113..2a3e3e0f 100644 --- a/examples/httpbin/validate_test.py +++ b/examples/httpbin/validate_test.py @@ -1,4 +1,4 @@ -# NOTE: Generated By HttpRunner v4.0.0-beta +# NOTE: Generated By HttpRunner v4.0.0 # FROM: validate.yml diff --git a/examples/postman_echo/cookie_manipulation/hardcode_test.py b/examples/postman_echo/cookie_manipulation/hardcode_test.py index f8d58a4d..efc33f10 100644 --- a/examples/postman_echo/cookie_manipulation/hardcode_test.py +++ b/examples/postman_echo/cookie_manipulation/hardcode_test.py @@ -1,4 +1,4 @@ -# NOTE: Generated By HttpRunner v4.0.0-beta +# NOTE: Generated By HttpRunner v4.0.0 # FROM: cookie_manipulation/hardcode.yml diff --git a/examples/postman_echo/cookie_manipulation/set_delete_cookies_test.py b/examples/postman_echo/cookie_manipulation/set_delete_cookies_test.py index 371a5372..73d9928d 100644 --- a/examples/postman_echo/cookie_manipulation/set_delete_cookies_test.py +++ b/examples/postman_echo/cookie_manipulation/set_delete_cookies_test.py @@ -1,4 +1,4 @@ -# NOTE: Generated By HttpRunner v4.0.0-beta +# NOTE: Generated By HttpRunner v4.0.0 # FROM: cookie_manipulation/set_delete_cookies.yml diff --git a/examples/postman_echo/request_methods/hardcode_test.py b/examples/postman_echo/request_methods/hardcode_test.py index a2b49836..a7fa8d14 100644 --- a/examples/postman_echo/request_methods/hardcode_test.py +++ b/examples/postman_echo/request_methods/hardcode_test.py @@ -1,4 +1,4 @@ -# NOTE: Generated By HttpRunner v4.0.0-beta +# NOTE: Generated By HttpRunner v4.0.0 # FROM: request_methods/hardcode.yml diff --git a/examples/postman_echo/request_methods/request_with_functions_test.py b/examples/postman_echo/request_methods/request_with_functions_test.py index 8688765f..a6f8ce0b 100644 --- a/examples/postman_echo/request_methods/request_with_functions_test.py +++ b/examples/postman_echo/request_methods/request_with_functions_test.py @@ -1,4 +1,4 @@ -# NOTE: Generated By HttpRunner v4.0.0-beta +# NOTE: Generated By HttpRunner v4.0.0 # FROM: request_methods/request_with_functions.yml diff --git a/examples/postman_echo/request_methods/request_with_parameters_test.py b/examples/postman_echo/request_methods/request_with_parameters_test.py index 5c6271a8..56e7c249 100644 --- a/examples/postman_echo/request_methods/request_with_parameters_test.py +++ b/examples/postman_echo/request_methods/request_with_parameters_test.py @@ -1,4 +1,4 @@ -# NOTE: Generated By HttpRunner v4.0.0-beta +# NOTE: Generated By HttpRunner v4.0.0 # FROM: request_methods/request_with_parameters.yml diff --git a/examples/postman_echo/request_methods/request_with_testcase_reference_test.py b/examples/postman_echo/request_methods/request_with_testcase_reference_test.py index 6a55b63a..2ed2b6e9 100644 --- a/examples/postman_echo/request_methods/request_with_testcase_reference_test.py +++ b/examples/postman_echo/request_methods/request_with_testcase_reference_test.py @@ -1,4 +1,4 @@ -# NOTE: Generated By HttpRunner v4.0.0-beta +# NOTE: Generated By HttpRunner v4.0.0 # FROM: request_methods/request_with_testcase_reference.yml diff --git a/examples/postman_echo/request_methods/request_with_variables_test.py b/examples/postman_echo/request_methods/request_with_variables_test.py index 0c7c0f77..788a0a85 100644 --- a/examples/postman_echo/request_methods/request_with_variables_test.py +++ b/examples/postman_echo/request_methods/request_with_variables_test.py @@ -1,4 +1,4 @@ -# NOTE: Generated By HttpRunner v4.0.0-beta +# NOTE: Generated By HttpRunner v4.0.0 # FROM: request_methods/request_with_variables.yml diff --git a/examples/postman_echo/request_methods/validate_with_functions_test.py b/examples/postman_echo/request_methods/validate_with_functions_test.py index 2ad1b58f..0b1f8966 100644 --- a/examples/postman_echo/request_methods/validate_with_functions_test.py +++ b/examples/postman_echo/request_methods/validate_with_functions_test.py @@ -1,4 +1,4 @@ -# NOTE: Generated By HttpRunner v4.0.0-beta +# NOTE: Generated By HttpRunner v4.0.0 # FROM: request_methods/validate_with_functions.yml diff --git a/examples/postman_echo/request_methods/validate_with_variables_test.py b/examples/postman_echo/request_methods/validate_with_variables_test.py index 26fa76bc..36b500a7 100644 --- a/examples/postman_echo/request_methods/validate_with_variables_test.py +++ b/examples/postman_echo/request_methods/validate_with_variables_test.py @@ -1,4 +1,4 @@ -# NOTE: Generated By HttpRunner v4.0.0-beta +# NOTE: Generated By HttpRunner v4.0.0 # FROM: request_methods/validate_with_variables.yml diff --git a/hrp/internal/scaffold/templates/plugin/debugtalk.go b/hrp/internal/scaffold/templates/plugin/debugtalk.go index dbb37554..b3b39400 100644 --- a/hrp/internal/scaffold/templates/plugin/debugtalk.go +++ b/hrp/internal/scaffold/templates/plugin/debugtalk.go @@ -42,11 +42,11 @@ func TeardownHookExample(args string) string { } func GetVersion() string { - return "v4.0.0-beta" + return fungo.Version } func main() { - fungo.Register("get_httprunner_version", GetVersion) + fungo.Register("get_version", GetVersion) fungo.Register("sum_ints", SumInts) fungo.Register("sum_two_int", SumTwoInt) fungo.Register("sum_two", SumTwoInt) diff --git a/hrp/internal/scaffold/templates/plugin/debugtalk.py b/hrp/internal/scaffold/templates/plugin/debugtalk.py index 180725e0..9fd41120 100644 --- a/hrp/internal/scaffold/templates/plugin/debugtalk.py +++ b/hrp/internal/scaffold/templates/plugin/debugtalk.py @@ -5,8 +5,8 @@ from typing import List import funppy -def get_httprunner_version(): - return "v4.0.0-beta" +def get_version(): + return funppy.__version__ def sleep(n_secs): @@ -60,7 +60,7 @@ def teardown_hook_example(name): if __name__ == "__main__": - funppy.register("get_httprunner_version", get_httprunner_version) + funppy.register("get_version", get_version) funppy.register("sum", sum) funppy.register("sum_ints", sum_ints) funppy.register("concatenate", concatenate) diff --git a/hrp/internal/scaffold/templates/testcases/demo_ref_testcase.yml b/hrp/internal/scaffold/templates/testcases/demo_ref_testcase.yml index 7c9bcd19..0743488e 100644 --- a/hrp/internal/scaffold/templates/testcases/demo_ref_testcase.yml +++ b/hrp/internal/scaffold/templates/testcases/demo_ref_testcase.yml @@ -24,7 +24,7 @@ teststeps: method: POST url: /post headers: - User-Agent: HttpRunner/${get_httprunner_version()} + User-Agent: funplugin/${get_version()} Content-Type: "application/x-www-form-urlencoded" data: "foo1=$foo1&foo2=$foo3" validate: diff --git a/hrp/internal/scaffold/templates/testcases/demo_ref_testcase_test.py b/hrp/internal/scaffold/templates/testcases/demo_ref_testcase_test.py index deae0a81..714030cd 100644 --- a/hrp/internal/scaffold/templates/testcases/demo_ref_testcase_test.py +++ b/hrp/internal/scaffold/templates/testcases/demo_ref_testcase_test.py @@ -1,4 +1,4 @@ -# NOTE: Generated By HttpRunner v4.0.0-beta +# NOTE: Generated By HttpRunner v4.0.0 # FROM: testcases/demo_ref_testcase.yml @@ -43,7 +43,7 @@ class TestCaseDemoRefTestcase(HttpRunner): .post("/post") .with_headers( **{ - "User-Agent": "HttpRunner/${get_httprunner_version()}", + "User-Agent": "funplugin/${get_version()}", "Content-Type": "application/x-www-form-urlencoded", } ) diff --git a/hrp/internal/scaffold/templates/testcases/demo_requests.yml b/hrp/internal/scaffold/templates/testcases/demo_requests.yml index cae1b491..86d1b9cc 100644 --- a/hrp/internal/scaffold/templates/testcases/demo_requests.yml +++ b/hrp/internal/scaffold/templates/testcases/demo_requests.yml @@ -24,7 +24,7 @@ teststeps: foo2: $foo2 sum_v: $sum_v headers: - User-Agent: HttpRunner/${get_httprunner_version()} + User-Agent: funplugin/${get_version()} extract: foo3: "body.args.foo2" validate: @@ -41,7 +41,7 @@ teststeps: method: POST url: /post headers: - User-Agent: HttpRunner/${get_httprunner_version()} + User-Agent: funplugin/${get_version()} Content-Type: "text/plain" data: "This is expected to be sent back as part of response body: $foo1-$foo2-$foo3." validate: @@ -55,7 +55,7 @@ teststeps: method: POST url: /post headers: - User-Agent: HttpRunner/${get_httprunner_version()} + User-Agent: funplugin/${get_version()} Content-Type: "application/x-www-form-urlencoded" data: "foo1=$foo1&foo2=$foo2&foo3=$foo3" validate: diff --git a/hrp/internal/scaffold/templates/testcases/demo_requests_test.py b/hrp/internal/scaffold/templates/testcases/demo_requests_test.py index e54a7faa..fc2ad5bb 100644 --- a/hrp/internal/scaffold/templates/testcases/demo_requests_test.py +++ b/hrp/internal/scaffold/templates/testcases/demo_requests_test.py @@ -1,4 +1,4 @@ -# NOTE: Generated By HttpRunner v4.0.0-beta +# NOTE: Generated By HttpRunner v4.0.0 # FROM: testcases/demo_requests.yml @@ -30,7 +30,7 @@ class TestCaseDemoRequests(HttpRunner): ) .get("/get") .with_params(**{"foo1": "$foo1", "foo2": "$foo2", "sum_v": "$sum_v"}) - .with_headers(**{"User-Agent": "HttpRunner/${get_httprunner_version()}"}) + .with_headers(**{"User-Agent": "funplugin/${get_version()}"}) .extract() .with_jmespath("body.args.foo2", "foo3") .validate() @@ -45,7 +45,7 @@ class TestCaseDemoRequests(HttpRunner): .post("/post") .with_headers( **{ - "User-Agent": "HttpRunner/${get_httprunner_version()}", + "User-Agent": "funplugin/${get_version()}", "Content-Type": "text/plain", } ) @@ -65,7 +65,7 @@ class TestCaseDemoRequests(HttpRunner): .post("/post") .with_headers( **{ - "User-Agent": "HttpRunner/${get_httprunner_version()}", + "User-Agent": "funplugin/${get_version()}", "Content-Type": "application/x-www-form-urlencoded", } ) diff --git a/hrp/internal/version/VERSION b/hrp/internal/version/VERSION index 46390341..f684230d 100644 --- a/hrp/internal/version/VERSION +++ b/hrp/internal/version/VERSION @@ -1 +1 @@ -v4.0.0-beta \ No newline at end of file +v4.0.0 \ No newline at end of file diff --git a/httprunner/__init__.py b/httprunner/__init__.py index cbc40932..7d424fcb 100644 --- a/httprunner/__init__.py +++ b/httprunner/__init__.py @@ -1,4 +1,4 @@ -__version__ = "v4.0.0-beta" +__version__ = "v4.0.0" __description__ = "One-stop solution for HTTP(S) testing." from httprunner.config import Config diff --git a/httprunner/make.py b/httprunner/make.py index c2b3d3b2..75a4e783 100644 --- a/httprunner/make.py +++ b/httprunner/make.py @@ -33,7 +33,7 @@ pytest_files_made_cache_mapping: Dict[Text, Text] = {} pytest_files_run_set: Set = set() __TEMPLATE__ = jinja2.Template( - """# NOTE: Generated By HttpRunner v{{ version }} + """# NOTE: Generated By HttpRunner {{ version }} # FROM: {{ testcase_path }} {% if imports_list and diff_levels > 0 %} diff --git a/pyproject.toml b/pyproject.toml index c50b5036..c5943ad9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "httprunner" -version = "v4.0.0-beta" +version = "v4.0.0" description = "One-stop solution for HTTP(S) testing." license = "Apache-2.0" readme = "README.md" From 7c052770e576cd26c37e64dd1591e8cf5cfaf4a7 Mon Sep 17 00:00:00 2001 From: debugtalk <mail@debugtalk.com> Date: Tue, 3 May 2022 11:02:17 +0800 Subject: [PATCH 007/109] bump version to v4.0.0 --- README.md | 2 +- docs/CHANGELOG.md | 4 +- docs/cmd/hrp.md | 2 +- docs/cmd/hrp_boom.md | 2 +- docs/cmd/hrp_convert.md | 2 +- docs/cmd/hrp_har2case.md | 2 +- docs/cmd/hrp_pytest.md | 2 +- docs/cmd/hrp_run.md | 2 +- docs/cmd/hrp_startproject.md | 2 +- examples/data/a_b_c/T1_test.py | 2 +- examples/data/a_b_c/T2_3_test.py | 2 +- .../demo-with-go-plugin/plugin/debugtalk.go | 4 +- .../testcases/demo_ref_testcase.yml | 2 +- .../testcases/demo_requests.yml | 6 +- examples/demo-with-py-plugin/debugtalk.py | 6 +- .../demo-with-py-plugin/testcases/__init__.py | 1 - .../testcases/demo_ref_testcase.yml | 2 +- .../testcases/demo_ref_testcase_test.py | 60 -------------- .../testcases/demo_requests.yml | 6 +- .../testcases/demo_requests_test.py | 83 ------------------- examples/httpbin/basic_test.py | 2 +- examples/httpbin/hooks_test.py | 2 +- examples/httpbin/load_image_test.py | 2 +- examples/httpbin/upload_test.py | 2 +- examples/httpbin/validate_test.py | 2 +- .../cookie_manipulation/hardcode_test.py | 2 +- .../set_delete_cookies_test.py | 2 +- .../request_methods/hardcode_test.py | 2 +- .../request_with_functions_test.py | 2 +- .../request_with_parameters_test.py | 2 +- .../request_with_testcase_reference_test.py | 2 +- .../request_with_variables_test.py | 2 +- .../validate_with_functions_test.py | 2 +- .../validate_with_variables_test.py | 2 +- .../scaffold/templates/plugin/debugtalk.go | 4 +- .../scaffold/templates/plugin/debugtalk.py | 6 +- .../templates/testcases/demo_ref_testcase.yml | 2 +- .../testcases/demo_ref_testcase_test.py | 4 +- .../templates/testcases/demo_requests.yml | 6 +- .../templates/testcases/demo_requests_test.py | 8 +- hrp/internal/version/VERSION | 2 +- httprunner/__init__.py | 2 +- httprunner/make.py | 2 +- pyproject.toml | 2 +- 44 files changed, 59 insertions(+), 201 deletions(-) delete mode 100644 examples/demo-with-py-plugin/testcases/__init__.py delete mode 100644 examples/demo-with-py-plugin/testcases/demo_ref_testcase_test.py delete mode 100644 examples/demo-with-py-plugin/testcases/demo_requests_test.py diff --git a/README.md b/README.md index 5d7c2632..c63561ce 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ ## 核心特性 -- 网络协议:完整支持 HTTP(S)/1.1 和 HTTP/2,可扩展支持 WebSocket/TCP/RPC 等更多协议 +- 网络协议:完整支持 HTTP(S)/HTTP2/WebSocket,可扩展支持 TCP/UDP/RPC 等更多协议 - 多格式可选:测试用例支持 YAML/JSON/go test/pytest 格式,并且支持格式互相转换 - 双执行引擎:同时支持 golang/python 两个执行引擎,兼具 go 的高性能和 [pytest] 的丰富生态 - 录制 & 生成:可使用 [HAR]/Postman/Swagger/curl 等生成测试用例;基于链式调用的方法提示也可快速编写测试用例 diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 17ab8c0d..51fb4192 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -1,13 +1,15 @@ # Release History -## v4.0.0-beta2 (2022-04-25) +## v4.0.0 (2022-05-03) **go version** - feat: add builtin function `environ`/`ENV` - fix: demo function compatibility - fix #1240: losing host port in har2case +- fix: concurrent map write in parameterize - change: get hrp version from aliyun OSS file when installing +- change: report more load testing metrics to prometheus ## v4.0.0-beta (2022-04-24) diff --git a/docs/cmd/hrp.md b/docs/cmd/hrp.md index 4a0c55d0..ef528f9d 100644 --- a/docs/cmd/hrp.md +++ b/docs/cmd/hrp.md @@ -36,4 +36,4 @@ Copyright 2017 debugtalk * [hrp run](hrp_run.md) - run API test with go engine * [hrp startproject](hrp_startproject.md) - create a scaffold project -###### Auto generated by spf13/cobra on 24-Apr-2022 +###### Auto generated by spf13/cobra on 3-May-2022 diff --git a/docs/cmd/hrp_boom.md b/docs/cmd/hrp_boom.md index ebc6bf4c..fe95ddab 100644 --- a/docs/cmd/hrp_boom.md +++ b/docs/cmd/hrp_boom.md @@ -41,4 +41,4 @@ hrp boom [flags] * [hrp](hrp.md) - Next-Generation API Testing Solution. -###### Auto generated by spf13/cobra on 24-Apr-2022 +###### Auto generated by spf13/cobra on 3-May-2022 diff --git a/docs/cmd/hrp_convert.md b/docs/cmd/hrp_convert.md index 6ec6b49e..880bce0b 100644 --- a/docs/cmd/hrp_convert.md +++ b/docs/cmd/hrp_convert.md @@ -18,4 +18,4 @@ hrp convert $path... [flags] * [hrp](hrp.md) - Next-Generation API Testing Solution. -###### Auto generated by spf13/cobra on 24-Apr-2022 +###### Auto generated by spf13/cobra on 3-May-2022 diff --git a/docs/cmd/hrp_har2case.md b/docs/cmd/hrp_har2case.md index 0d15ec54..d7b6e925 100644 --- a/docs/cmd/hrp_har2case.md +++ b/docs/cmd/hrp_har2case.md @@ -24,4 +24,4 @@ hrp har2case $har_path... [flags] * [hrp](hrp.md) - Next-Generation API Testing Solution. -###### Auto generated by spf13/cobra on 24-Apr-2022 +###### Auto generated by spf13/cobra on 3-May-2022 diff --git a/docs/cmd/hrp_pytest.md b/docs/cmd/hrp_pytest.md index bc0c2437..5c59d217 100644 --- a/docs/cmd/hrp_pytest.md +++ b/docs/cmd/hrp_pytest.md @@ -16,4 +16,4 @@ hrp pytest $path ... [flags] * [hrp](hrp.md) - Next-Generation API Testing Solution. -###### Auto generated by spf13/cobra on 24-Apr-2022 +###### Auto generated by spf13/cobra on 3-May-2022 diff --git a/docs/cmd/hrp_run.md b/docs/cmd/hrp_run.md index d7581b4c..6d2f8486 100644 --- a/docs/cmd/hrp_run.md +++ b/docs/cmd/hrp_run.md @@ -34,4 +34,4 @@ hrp run $path... [flags] * [hrp](hrp.md) - Next-Generation API Testing Solution. -###### Auto generated by spf13/cobra on 24-Apr-2022 +###### Auto generated by spf13/cobra on 3-May-2022 diff --git a/docs/cmd/hrp_startproject.md b/docs/cmd/hrp_startproject.md index 3402b1c9..a8706619 100644 --- a/docs/cmd/hrp_startproject.md +++ b/docs/cmd/hrp_startproject.md @@ -20,4 +20,4 @@ hrp startproject $project_name [flags] * [hrp](hrp.md) - Next-Generation API Testing Solution. -###### Auto generated by spf13/cobra on 24-Apr-2022 +###### Auto generated by spf13/cobra on 3-May-2022 diff --git a/examples/data/a_b_c/T1_test.py b/examples/data/a_b_c/T1_test.py index d3273df7..504b3f6d 100644 --- a/examples/data/a_b_c/T1_test.py +++ b/examples/data/a_b_c/T1_test.py @@ -1,4 +1,4 @@ -# NOTE: Generated By HttpRunner v4.0.0-beta +# NOTE: Generated By HttpRunner v4.0.0 # FROM: a-b.c/1.yml diff --git a/examples/data/a_b_c/T2_3_test.py b/examples/data/a_b_c/T2_3_test.py index a225b4cb..452927a9 100644 --- a/examples/data/a_b_c/T2_3_test.py +++ b/examples/data/a_b_c/T2_3_test.py @@ -1,4 +1,4 @@ -# NOTE: Generated By HttpRunner v4.0.0-beta +# NOTE: Generated By HttpRunner v4.0.0 # FROM: a-b.c/2 3.yml diff --git a/examples/demo-with-go-plugin/plugin/debugtalk.go b/examples/demo-with-go-plugin/plugin/debugtalk.go index dbb37554..b3b39400 100644 --- a/examples/demo-with-go-plugin/plugin/debugtalk.go +++ b/examples/demo-with-go-plugin/plugin/debugtalk.go @@ -42,11 +42,11 @@ func TeardownHookExample(args string) string { } func GetVersion() string { - return "v4.0.0-beta" + return fungo.Version } func main() { - fungo.Register("get_httprunner_version", GetVersion) + fungo.Register("get_version", GetVersion) fungo.Register("sum_ints", SumInts) fungo.Register("sum_two_int", SumTwoInt) fungo.Register("sum_two", SumTwoInt) diff --git a/examples/demo-with-go-plugin/testcases/demo_ref_testcase.yml b/examples/demo-with-go-plugin/testcases/demo_ref_testcase.yml index 7c9bcd19..0743488e 100644 --- a/examples/demo-with-go-plugin/testcases/demo_ref_testcase.yml +++ b/examples/demo-with-go-plugin/testcases/demo_ref_testcase.yml @@ -24,7 +24,7 @@ teststeps: method: POST url: /post headers: - User-Agent: HttpRunner/${get_httprunner_version()} + User-Agent: funplugin/${get_version()} Content-Type: "application/x-www-form-urlencoded" data: "foo1=$foo1&foo2=$foo3" validate: diff --git a/examples/demo-with-go-plugin/testcases/demo_requests.yml b/examples/demo-with-go-plugin/testcases/demo_requests.yml index cae1b491..86d1b9cc 100644 --- a/examples/demo-with-go-plugin/testcases/demo_requests.yml +++ b/examples/demo-with-go-plugin/testcases/demo_requests.yml @@ -24,7 +24,7 @@ teststeps: foo2: $foo2 sum_v: $sum_v headers: - User-Agent: HttpRunner/${get_httprunner_version()} + User-Agent: funplugin/${get_version()} extract: foo3: "body.args.foo2" validate: @@ -41,7 +41,7 @@ teststeps: method: POST url: /post headers: - User-Agent: HttpRunner/${get_httprunner_version()} + User-Agent: funplugin/${get_version()} Content-Type: "text/plain" data: "This is expected to be sent back as part of response body: $foo1-$foo2-$foo3." validate: @@ -55,7 +55,7 @@ teststeps: method: POST url: /post headers: - User-Agent: HttpRunner/${get_httprunner_version()} + User-Agent: funplugin/${get_version()} Content-Type: "application/x-www-form-urlencoded" data: "foo1=$foo1&foo2=$foo2&foo3=$foo3" validate: diff --git a/examples/demo-with-py-plugin/debugtalk.py b/examples/demo-with-py-plugin/debugtalk.py index 180725e0..9fd41120 100644 --- a/examples/demo-with-py-plugin/debugtalk.py +++ b/examples/demo-with-py-plugin/debugtalk.py @@ -5,8 +5,8 @@ from typing import List import funppy -def get_httprunner_version(): - return "v4.0.0-beta" +def get_version(): + return funppy.__version__ def sleep(n_secs): @@ -60,7 +60,7 @@ def teardown_hook_example(name): if __name__ == "__main__": - funppy.register("get_httprunner_version", get_httprunner_version) + funppy.register("get_version", get_version) funppy.register("sum", sum) funppy.register("sum_ints", sum_ints) funppy.register("concatenate", concatenate) diff --git a/examples/demo-with-py-plugin/testcases/__init__.py b/examples/demo-with-py-plugin/testcases/__init__.py deleted file mode 100644 index 70cfba53..00000000 --- a/examples/demo-with-py-plugin/testcases/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# NOTICE: Generated By HttpRunner. DO NOT EDIT! diff --git a/examples/demo-with-py-plugin/testcases/demo_ref_testcase.yml b/examples/demo-with-py-plugin/testcases/demo_ref_testcase.yml index 7c9bcd19..0743488e 100644 --- a/examples/demo-with-py-plugin/testcases/demo_ref_testcase.yml +++ b/examples/demo-with-py-plugin/testcases/demo_ref_testcase.yml @@ -24,7 +24,7 @@ teststeps: method: POST url: /post headers: - User-Agent: HttpRunner/${get_httprunner_version()} + User-Agent: funplugin/${get_version()} Content-Type: "application/x-www-form-urlencoded" data: "foo1=$foo1&foo2=$foo3" validate: diff --git a/examples/demo-with-py-plugin/testcases/demo_ref_testcase_test.py b/examples/demo-with-py-plugin/testcases/demo_ref_testcase_test.py deleted file mode 100644 index e88b8d08..00000000 --- a/examples/demo-with-py-plugin/testcases/demo_ref_testcase_test.py +++ /dev/null @@ -1,60 +0,0 @@ -# NOTE: Generated By HttpRunner v3.1.11 -# FROM: testcases/demo_ref_testcase.yml - - -import sys -from pathlib import Path - -sys.path.insert(0, str(Path(__file__).parent.parent)) - - -from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCase - -from testcases.demo_requests_test import TestCaseDemoRequests as DemoRequests - - -class TestCaseDemoRefTestcase(HttpRunner): - - config = ( - Config("request methods testcase: reference testcase") - .variables( - **{ - "foo1": "testsuite_config_bar1", - "expect_foo1": "testsuite_config_bar1", - "expect_foo2": "config_bar2", - } - ) - .base_url("https://postman-echo.com") - .verify(False) - ) - - teststeps = [ - Step( - RunTestCase("request with functions") - .with_variables( - **{"foo1": "testcase_ref_bar1", "expect_foo1": "testcase_ref_bar1"} - ) - .call(DemoRequests) - .export(*["foo3"]) - ), - Step( - RunRequest("post form data") - .with_variables(**{"foo1": "bar1"}) - .post("/post") - .with_headers( - **{ - "User-Agent": "HttpRunner/${get_httprunner_version()}", - "Content-Type": "application/x-www-form-urlencoded", - } - ) - .with_data("foo1=$foo1&foo2=$foo3") - .validate() - .assert_equal("status_code", 200) - .assert_equal("body.form.foo1", "bar1") - .assert_equal("body.form.foo2", "bar21") - ), - ] - - -if __name__ == "__main__": - TestCaseDemoRefTestcase().test_start() diff --git a/examples/demo-with-py-plugin/testcases/demo_requests.yml b/examples/demo-with-py-plugin/testcases/demo_requests.yml index cae1b491..86d1b9cc 100644 --- a/examples/demo-with-py-plugin/testcases/demo_requests.yml +++ b/examples/demo-with-py-plugin/testcases/demo_requests.yml @@ -24,7 +24,7 @@ teststeps: foo2: $foo2 sum_v: $sum_v headers: - User-Agent: HttpRunner/${get_httprunner_version()} + User-Agent: funplugin/${get_version()} extract: foo3: "body.args.foo2" validate: @@ -41,7 +41,7 @@ teststeps: method: POST url: /post headers: - User-Agent: HttpRunner/${get_httprunner_version()} + User-Agent: funplugin/${get_version()} Content-Type: "text/plain" data: "This is expected to be sent back as part of response body: $foo1-$foo2-$foo3." validate: @@ -55,7 +55,7 @@ teststeps: method: POST url: /post headers: - User-Agent: HttpRunner/${get_httprunner_version()} + User-Agent: funplugin/${get_version()} Content-Type: "application/x-www-form-urlencoded" data: "foo1=$foo1&foo2=$foo2&foo3=$foo3" validate: diff --git a/examples/demo-with-py-plugin/testcases/demo_requests_test.py b/examples/demo-with-py-plugin/testcases/demo_requests_test.py deleted file mode 100644 index b5c34bc2..00000000 --- a/examples/demo-with-py-plugin/testcases/demo_requests_test.py +++ /dev/null @@ -1,83 +0,0 @@ -# NOTE: Generated By HttpRunner v3.1.11 -# FROM: testcases/demo_requests.yml - - -from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCase - - -class TestCaseDemoRequests(HttpRunner): - - config = ( - Config("request methods testcase with functions") - .variables( - **{ - "foo1": "config_bar1", - "foo2": "config_bar2", - "expect_foo1": "config_bar1", - "expect_foo2": "config_bar2", - } - ) - .base_url("https://postman-echo.com") - .verify(False) - .export(*["foo3"]) - ) - - teststeps = [ - Step( - RunRequest("get with params") - .with_variables( - **{"foo1": "bar11", "foo2": "bar21", "sum_v": "${sum_two_int(1, 2)}"} - ) - .get("/get") - .with_params(**{"foo1": "$foo1", "foo2": "$foo2", "sum_v": "$sum_v"}) - .with_headers(**{"User-Agent": "HttpRunner/${get_httprunner_version()}"}) - .extract() - .with_jmespath("body.args.foo2", "foo3") - .validate() - .assert_equal("status_code", 200) - .assert_equal("body.args.foo1", "bar11") - .assert_equal("body.args.sum_v", "3") - .assert_equal("body.args.foo2", "bar21") - ), - Step( - RunRequest("post raw text") - .with_variables(**{"foo1": "bar12", "foo3": "bar32"}) - .post("/post") - .with_headers( - **{ - "User-Agent": "HttpRunner/${get_httprunner_version()}", - "Content-Type": "text/plain", - } - ) - .with_data( - "This is expected to be sent back as part of response body: $foo1-$foo2-$foo3." - ) - .validate() - .assert_equal("status_code", 200) - .assert_equal( - "body.data", - "This is expected to be sent back as part of response body: bar12-$expect_foo2-bar32.", - ) - ), - Step( - RunRequest("post form data") - .with_variables(**{"foo2": "bar23"}) - .post("/post") - .with_headers( - **{ - "User-Agent": "HttpRunner/${get_httprunner_version()}", - "Content-Type": "application/x-www-form-urlencoded", - } - ) - .with_data("foo1=$foo1&foo2=$foo2&foo3=$foo3") - .validate() - .assert_equal("status_code", 200) - .assert_equal("body.form.foo1", "$expect_foo1") - .assert_equal("body.form.foo2", "bar23") - .assert_equal("body.form.foo3", "bar21") - ), - ] - - -if __name__ == "__main__": - TestCaseDemoRequests().test_start() diff --git a/examples/httpbin/basic_test.py b/examples/httpbin/basic_test.py index d13c87ac..42733b08 100644 --- a/examples/httpbin/basic_test.py +++ b/examples/httpbin/basic_test.py @@ -1,4 +1,4 @@ -# NOTE: Generated By HttpRunner v4.0.0-beta +# NOTE: Generated By HttpRunner v4.0.0 # FROM: basic.yml diff --git a/examples/httpbin/hooks_test.py b/examples/httpbin/hooks_test.py index 96302d35..cb249adb 100644 --- a/examples/httpbin/hooks_test.py +++ b/examples/httpbin/hooks_test.py @@ -1,4 +1,4 @@ -# NOTE: Generated By HttpRunner v4.0.0-beta +# NOTE: Generated By HttpRunner v4.0.0 # FROM: hooks.yml diff --git a/examples/httpbin/load_image_test.py b/examples/httpbin/load_image_test.py index a43f5e75..9c56466c 100644 --- a/examples/httpbin/load_image_test.py +++ b/examples/httpbin/load_image_test.py @@ -1,4 +1,4 @@ -# NOTE: Generated By HttpRunner v4.0.0-beta +# NOTE: Generated By HttpRunner v4.0.0 # FROM: load_image.yml diff --git a/examples/httpbin/upload_test.py b/examples/httpbin/upload_test.py index 394ecbb6..56d2c1c8 100644 --- a/examples/httpbin/upload_test.py +++ b/examples/httpbin/upload_test.py @@ -1,4 +1,4 @@ -# NOTE: Generated By HttpRunner v4.0.0-beta +# NOTE: Generated By HttpRunner v4.0.0 # FROM: upload.yml diff --git a/examples/httpbin/validate_test.py b/examples/httpbin/validate_test.py index ae948113..2a3e3e0f 100644 --- a/examples/httpbin/validate_test.py +++ b/examples/httpbin/validate_test.py @@ -1,4 +1,4 @@ -# NOTE: Generated By HttpRunner v4.0.0-beta +# NOTE: Generated By HttpRunner v4.0.0 # FROM: validate.yml diff --git a/examples/postman_echo/cookie_manipulation/hardcode_test.py b/examples/postman_echo/cookie_manipulation/hardcode_test.py index f8d58a4d..efc33f10 100644 --- a/examples/postman_echo/cookie_manipulation/hardcode_test.py +++ b/examples/postman_echo/cookie_manipulation/hardcode_test.py @@ -1,4 +1,4 @@ -# NOTE: Generated By HttpRunner v4.0.0-beta +# NOTE: Generated By HttpRunner v4.0.0 # FROM: cookie_manipulation/hardcode.yml diff --git a/examples/postman_echo/cookie_manipulation/set_delete_cookies_test.py b/examples/postman_echo/cookie_manipulation/set_delete_cookies_test.py index 371a5372..73d9928d 100644 --- a/examples/postman_echo/cookie_manipulation/set_delete_cookies_test.py +++ b/examples/postman_echo/cookie_manipulation/set_delete_cookies_test.py @@ -1,4 +1,4 @@ -# NOTE: Generated By HttpRunner v4.0.0-beta +# NOTE: Generated By HttpRunner v4.0.0 # FROM: cookie_manipulation/set_delete_cookies.yml diff --git a/examples/postman_echo/request_methods/hardcode_test.py b/examples/postman_echo/request_methods/hardcode_test.py index a2b49836..a7fa8d14 100644 --- a/examples/postman_echo/request_methods/hardcode_test.py +++ b/examples/postman_echo/request_methods/hardcode_test.py @@ -1,4 +1,4 @@ -# NOTE: Generated By HttpRunner v4.0.0-beta +# NOTE: Generated By HttpRunner v4.0.0 # FROM: request_methods/hardcode.yml diff --git a/examples/postman_echo/request_methods/request_with_functions_test.py b/examples/postman_echo/request_methods/request_with_functions_test.py index 8688765f..a6f8ce0b 100644 --- a/examples/postman_echo/request_methods/request_with_functions_test.py +++ b/examples/postman_echo/request_methods/request_with_functions_test.py @@ -1,4 +1,4 @@ -# NOTE: Generated By HttpRunner v4.0.0-beta +# NOTE: Generated By HttpRunner v4.0.0 # FROM: request_methods/request_with_functions.yml diff --git a/examples/postman_echo/request_methods/request_with_parameters_test.py b/examples/postman_echo/request_methods/request_with_parameters_test.py index 5c6271a8..56e7c249 100644 --- a/examples/postman_echo/request_methods/request_with_parameters_test.py +++ b/examples/postman_echo/request_methods/request_with_parameters_test.py @@ -1,4 +1,4 @@ -# NOTE: Generated By HttpRunner v4.0.0-beta +# NOTE: Generated By HttpRunner v4.0.0 # FROM: request_methods/request_with_parameters.yml diff --git a/examples/postman_echo/request_methods/request_with_testcase_reference_test.py b/examples/postman_echo/request_methods/request_with_testcase_reference_test.py index 6a55b63a..2ed2b6e9 100644 --- a/examples/postman_echo/request_methods/request_with_testcase_reference_test.py +++ b/examples/postman_echo/request_methods/request_with_testcase_reference_test.py @@ -1,4 +1,4 @@ -# NOTE: Generated By HttpRunner v4.0.0-beta +# NOTE: Generated By HttpRunner v4.0.0 # FROM: request_methods/request_with_testcase_reference.yml diff --git a/examples/postman_echo/request_methods/request_with_variables_test.py b/examples/postman_echo/request_methods/request_with_variables_test.py index 0c7c0f77..788a0a85 100644 --- a/examples/postman_echo/request_methods/request_with_variables_test.py +++ b/examples/postman_echo/request_methods/request_with_variables_test.py @@ -1,4 +1,4 @@ -# NOTE: Generated By HttpRunner v4.0.0-beta +# NOTE: Generated By HttpRunner v4.0.0 # FROM: request_methods/request_with_variables.yml diff --git a/examples/postman_echo/request_methods/validate_with_functions_test.py b/examples/postman_echo/request_methods/validate_with_functions_test.py index 2ad1b58f..0b1f8966 100644 --- a/examples/postman_echo/request_methods/validate_with_functions_test.py +++ b/examples/postman_echo/request_methods/validate_with_functions_test.py @@ -1,4 +1,4 @@ -# NOTE: Generated By HttpRunner v4.0.0-beta +# NOTE: Generated By HttpRunner v4.0.0 # FROM: request_methods/validate_with_functions.yml diff --git a/examples/postman_echo/request_methods/validate_with_variables_test.py b/examples/postman_echo/request_methods/validate_with_variables_test.py index 26fa76bc..36b500a7 100644 --- a/examples/postman_echo/request_methods/validate_with_variables_test.py +++ b/examples/postman_echo/request_methods/validate_with_variables_test.py @@ -1,4 +1,4 @@ -# NOTE: Generated By HttpRunner v4.0.0-beta +# NOTE: Generated By HttpRunner v4.0.0 # FROM: request_methods/validate_with_variables.yml diff --git a/hrp/internal/scaffold/templates/plugin/debugtalk.go b/hrp/internal/scaffold/templates/plugin/debugtalk.go index dbb37554..b3b39400 100644 --- a/hrp/internal/scaffold/templates/plugin/debugtalk.go +++ b/hrp/internal/scaffold/templates/plugin/debugtalk.go @@ -42,11 +42,11 @@ func TeardownHookExample(args string) string { } func GetVersion() string { - return "v4.0.0-beta" + return fungo.Version } func main() { - fungo.Register("get_httprunner_version", GetVersion) + fungo.Register("get_version", GetVersion) fungo.Register("sum_ints", SumInts) fungo.Register("sum_two_int", SumTwoInt) fungo.Register("sum_two", SumTwoInt) diff --git a/hrp/internal/scaffold/templates/plugin/debugtalk.py b/hrp/internal/scaffold/templates/plugin/debugtalk.py index 180725e0..9fd41120 100644 --- a/hrp/internal/scaffold/templates/plugin/debugtalk.py +++ b/hrp/internal/scaffold/templates/plugin/debugtalk.py @@ -5,8 +5,8 @@ from typing import List import funppy -def get_httprunner_version(): - return "v4.0.0-beta" +def get_version(): + return funppy.__version__ def sleep(n_secs): @@ -60,7 +60,7 @@ def teardown_hook_example(name): if __name__ == "__main__": - funppy.register("get_httprunner_version", get_httprunner_version) + funppy.register("get_version", get_version) funppy.register("sum", sum) funppy.register("sum_ints", sum_ints) funppy.register("concatenate", concatenate) diff --git a/hrp/internal/scaffold/templates/testcases/demo_ref_testcase.yml b/hrp/internal/scaffold/templates/testcases/demo_ref_testcase.yml index 7c9bcd19..0743488e 100644 --- a/hrp/internal/scaffold/templates/testcases/demo_ref_testcase.yml +++ b/hrp/internal/scaffold/templates/testcases/demo_ref_testcase.yml @@ -24,7 +24,7 @@ teststeps: method: POST url: /post headers: - User-Agent: HttpRunner/${get_httprunner_version()} + User-Agent: funplugin/${get_version()} Content-Type: "application/x-www-form-urlencoded" data: "foo1=$foo1&foo2=$foo3" validate: diff --git a/hrp/internal/scaffold/templates/testcases/demo_ref_testcase_test.py b/hrp/internal/scaffold/templates/testcases/demo_ref_testcase_test.py index deae0a81..714030cd 100644 --- a/hrp/internal/scaffold/templates/testcases/demo_ref_testcase_test.py +++ b/hrp/internal/scaffold/templates/testcases/demo_ref_testcase_test.py @@ -1,4 +1,4 @@ -# NOTE: Generated By HttpRunner v4.0.0-beta +# NOTE: Generated By HttpRunner v4.0.0 # FROM: testcases/demo_ref_testcase.yml @@ -43,7 +43,7 @@ class TestCaseDemoRefTestcase(HttpRunner): .post("/post") .with_headers( **{ - "User-Agent": "HttpRunner/${get_httprunner_version()}", + "User-Agent": "funplugin/${get_version()}", "Content-Type": "application/x-www-form-urlencoded", } ) diff --git a/hrp/internal/scaffold/templates/testcases/demo_requests.yml b/hrp/internal/scaffold/templates/testcases/demo_requests.yml index cae1b491..86d1b9cc 100644 --- a/hrp/internal/scaffold/templates/testcases/demo_requests.yml +++ b/hrp/internal/scaffold/templates/testcases/demo_requests.yml @@ -24,7 +24,7 @@ teststeps: foo2: $foo2 sum_v: $sum_v headers: - User-Agent: HttpRunner/${get_httprunner_version()} + User-Agent: funplugin/${get_version()} extract: foo3: "body.args.foo2" validate: @@ -41,7 +41,7 @@ teststeps: method: POST url: /post headers: - User-Agent: HttpRunner/${get_httprunner_version()} + User-Agent: funplugin/${get_version()} Content-Type: "text/plain" data: "This is expected to be sent back as part of response body: $foo1-$foo2-$foo3." validate: @@ -55,7 +55,7 @@ teststeps: method: POST url: /post headers: - User-Agent: HttpRunner/${get_httprunner_version()} + User-Agent: funplugin/${get_version()} Content-Type: "application/x-www-form-urlencoded" data: "foo1=$foo1&foo2=$foo2&foo3=$foo3" validate: diff --git a/hrp/internal/scaffold/templates/testcases/demo_requests_test.py b/hrp/internal/scaffold/templates/testcases/demo_requests_test.py index e54a7faa..fc2ad5bb 100644 --- a/hrp/internal/scaffold/templates/testcases/demo_requests_test.py +++ b/hrp/internal/scaffold/templates/testcases/demo_requests_test.py @@ -1,4 +1,4 @@ -# NOTE: Generated By HttpRunner v4.0.0-beta +# NOTE: Generated By HttpRunner v4.0.0 # FROM: testcases/demo_requests.yml @@ -30,7 +30,7 @@ class TestCaseDemoRequests(HttpRunner): ) .get("/get") .with_params(**{"foo1": "$foo1", "foo2": "$foo2", "sum_v": "$sum_v"}) - .with_headers(**{"User-Agent": "HttpRunner/${get_httprunner_version()}"}) + .with_headers(**{"User-Agent": "funplugin/${get_version()}"}) .extract() .with_jmespath("body.args.foo2", "foo3") .validate() @@ -45,7 +45,7 @@ class TestCaseDemoRequests(HttpRunner): .post("/post") .with_headers( **{ - "User-Agent": "HttpRunner/${get_httprunner_version()}", + "User-Agent": "funplugin/${get_version()}", "Content-Type": "text/plain", } ) @@ -65,7 +65,7 @@ class TestCaseDemoRequests(HttpRunner): .post("/post") .with_headers( **{ - "User-Agent": "HttpRunner/${get_httprunner_version()}", + "User-Agent": "funplugin/${get_version()}", "Content-Type": "application/x-www-form-urlencoded", } ) diff --git a/hrp/internal/version/VERSION b/hrp/internal/version/VERSION index 46390341..f684230d 100644 --- a/hrp/internal/version/VERSION +++ b/hrp/internal/version/VERSION @@ -1 +1 @@ -v4.0.0-beta \ No newline at end of file +v4.0.0 \ No newline at end of file diff --git a/httprunner/__init__.py b/httprunner/__init__.py index cbc40932..7d424fcb 100644 --- a/httprunner/__init__.py +++ b/httprunner/__init__.py @@ -1,4 +1,4 @@ -__version__ = "v4.0.0-beta" +__version__ = "v4.0.0" __description__ = "One-stop solution for HTTP(S) testing." from httprunner.config import Config diff --git a/httprunner/make.py b/httprunner/make.py index c2b3d3b2..75a4e783 100644 --- a/httprunner/make.py +++ b/httprunner/make.py @@ -33,7 +33,7 @@ pytest_files_made_cache_mapping: Dict[Text, Text] = {} pytest_files_run_set: Set = set() __TEMPLATE__ = jinja2.Template( - """# NOTE: Generated By HttpRunner v{{ version }} + """# NOTE: Generated By HttpRunner {{ version }} # FROM: {{ testcase_path }} {% if imports_list and diff_levels > 0 %} diff --git a/pyproject.toml b/pyproject.toml index c50b5036..c5943ad9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "httprunner" -version = "v4.0.0-beta" +version = "v4.0.0" description = "One-stop solution for HTTP(S) testing." license = "Apache-2.0" readme = "README.md" From e7928e49951b7736cbdec1699e27b4959e1a3ab1 Mon Sep 17 00:00:00 2001 From: "duanchao.bill" <duanchao.bill@bytedance.com> Date: Wed, 4 May 2022 10:29:11 +0800 Subject: [PATCH 008/109] fix: add cython sqlalchemy https://github.com/httprunner/httprunner/runs/6208464792?check_suite_focus=true https://github.com/httprunner/httprunner/actions/runs/2238263528 --- poetry.lock | 202 ++++++++++++++++++++++++++++++++++++++++++++++++- pyproject.toml | 2 + 2 files changed, 203 insertions(+), 1 deletion(-) diff --git a/poetry.lock b/poetry.lock index d43d7ad6..8c1ec1e6 100644 --- a/poetry.lock +++ b/poetry.lock @@ -179,6 +179,19 @@ type = "legacy" url = "https://pypi.tuna.tsinghua.edu.cn/simple" reference = "tsinghua" +[[package]] +name = "cython" +version = "0.29.28" +description = "The Cython compiler for writing C extensions for the Python language." +category = "main" +optional = false +python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + [[package]] name = "filetype" version = "1.0.10" @@ -192,6 +205,22 @@ type = "legacy" url = "https://pypi.tuna.tsinghua.edu.cn/simple" reference = "tsinghua" +[[package]] +name = "greenlet" +version = "1.1.2" +description = "Lightweight in-process concurrent programming" +category = "main" +optional = false +python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*" + +[package.extras] +docs = ["sphinx"] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + [[package]] name = "idna" version = "3.3" @@ -602,6 +631,44 @@ type = "legacy" url = "https://pypi.tuna.tsinghua.edu.cn/simple" reference = "tsinghua" +[[package]] +name = "sqlalchemy" +version = "1.4.36" +description = "Database Abstraction Library" +category = "main" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" + +[package.dependencies] +greenlet = {version = "!=0.4.17", markers = "python_version >= \"3\" and (platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\")"} +importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} + +[package.extras] +aiomysql = ["greenlet (!=0.4.17)", "aiomysql"] +aiosqlite = ["typing_extensions (!=3.10.0.1)", "greenlet (!=0.4.17)", "aiosqlite"] +asyncio = ["greenlet (!=0.4.17)"] +asyncmy = ["greenlet (!=0.4.17)", "asyncmy (>=0.2.3,!=0.2.4)"] +mariadb_connector = ["mariadb (>=1.0.1)"] +mssql = ["pyodbc"] +mssql_pymssql = ["pymssql"] +mssql_pyodbc = ["pyodbc"] +mypy = ["sqlalchemy2-stubs", "mypy (>=0.910)"] +mysql = ["mysqlclient (>=1.4.0,<2)", "mysqlclient (>=1.4.0)"] +mysql_connector = ["mysql-connector-python"] +oracle = ["cx_oracle (>=7,<8)", "cx_oracle (>=7)"] +postgresql = ["psycopg2 (>=2.7)"] +postgresql_asyncpg = ["greenlet (!=0.4.17)", "asyncpg"] +postgresql_pg8000 = ["pg8000 (>=1.16.6)"] +postgresql_psycopg2binary = ["psycopg2-binary"] +postgresql_psycopg2cffi = ["psycopg2cffi"] +pymysql = ["pymysql (<1)", "pymysql"] +sqlcipher = ["sqlcipher3-binary"] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + [[package]] name = "thriftpy2" version = "0.4.14" @@ -732,7 +799,7 @@ upload = ["requests-toolbelt", "filetype"] [metadata] lock-version = "1.1" python-versions = "^3.7" -content-hash = "f521e769204610adda9594e1159c73c2215c6456c53e27ba1141e048d6aa8a54" +content-hash = "73bf6bfc1612775dd37dd94bb73b73d6de7a38bb3dad7c027c24f7d3d0209e62" [metadata.files] allure-pytest = [ @@ -890,10 +957,105 @@ coverage = [ {file = "coverage-4.5.4-cp38-cp38-macosx_10_13_x86_64.whl", hash = "sha256:141f08ed3c4b1847015e2cd62ec06d35e67a3ac185c26f7635f4406b90afa9c5"}, {file = "coverage-4.5.4.tar.gz", hash = "sha256:e07d9f1a23e9e93ab5c62902833bf3e4b1f65502927379148b6622686223125c"}, ] +cython = [ + {file = "Cython-0.29.28-cp27-cp27m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:75686c586e37b1fed0fe4a2c053474f96fc07da0063bbfc98023454540515d31"}, + {file = "Cython-0.29.28-cp27-cp27m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:16f2e74fcac223c53e298ecead62c353d3cffa107bea5d8232e4b2ba40781634"}, + {file = "Cython-0.29.28-cp27-cp27mu-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b6c77cc24861a33714e74212abfab4e54bf42e1ad602623f193b8e369389af2f"}, + {file = "Cython-0.29.28-cp27-cp27mu-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:59f4e86b415620a097cf0ec602adf5a7ee3cc33e8220567ded96566f753483f8"}, + {file = "Cython-0.29.28-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:31465dce7fd3f058d02afb98b13af962848cc607052388814428dc801cc26f57"}, + {file = "Cython-0.29.28-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:5658fa477e80d96c49d5ff011938dd4b62da9aa428f771b91f1a7c49af45aad8"}, + {file = "Cython-0.29.28-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_24_i686.whl", hash = "sha256:33b69ac9bbf2b93d8cae336cfe48889397a857e6ceeb5cef0b2f0b31b6c54f2b"}, + {file = "Cython-0.29.28-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:9d39ee7ddef6856413f950b8959e852d83376d9db1c509505e3f4873df32aa70"}, + {file = "Cython-0.29.28-cp35-cp35m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c9848a423a14e8f51bd4bbf8e2ff37031764ce66bdc7c6bc06c70d4084eb23c7"}, + {file = "Cython-0.29.28-cp35-cp35m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:09448aadb818387160ca4d1e1b82dbb7001526b6d0bed7529c4e8ac12e3b6f4c"}, + {file = "Cython-0.29.28-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:341917bdb2c95bcf8322aacfe50bbe6b4794880b16fa8b2300330520e123a5e5"}, + {file = "Cython-0.29.28-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:fdcef7abb09fd827691e3abe6fd42c6c34beaccfa0bc2df6074f0a49949df6a8"}, + {file = "Cython-0.29.28-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_24_i686.whl", hash = "sha256:43eca77169f855dd04be11921a585c8854a174f30bc925257e92bc7b9197fbd2"}, + {file = "Cython-0.29.28-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7962a78ceb80cdec21345fb5088e675060fa65982030d446069f2d675d30e3cd"}, + {file = "Cython-0.29.28-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:ed32c206e1d68056a34b21d2ec0cf0f23d338d6531476a68c73e21e20bd7bb63"}, + {file = "Cython-0.29.28-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:a0ed39c63ba52edd03a39ea9d6da6f5326aaee5d333c317feba543270a1b3af5"}, + {file = "Cython-0.29.28-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:ded4fd3da4dee2f4414c35214244e29befa7f6fede3e9be317e765169df2cbc7"}, + {file = "Cython-0.29.28-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:e24bd94946ffa37f30fcb865f2340fb6d429a3c7bf87b47b22f7d22e0e68a15c"}, + {file = "Cython-0.29.28-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_24_i686.whl", hash = "sha256:076aa8da83383e2bed0ca5f92c13a7e76e684bc41fe8e438bbed735f5b1c2731"}, + {file = "Cython-0.29.28-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:004387d8b94c64681ee05660d6a234e125396097726cf2f419c0fa2ac38034d6"}, + {file = "Cython-0.29.28-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:d6036f6a5a0c7fb1af88889872268b15bf20dd9cefe33a6602d79ba18b8db20f"}, + {file = "Cython-0.29.28-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:1612d7439590ba3b8de5f907bf0e54bd8e024eafb8c59261531a7988030c182d"}, + {file = "Cython-0.29.28-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:d7d7beb600d5dd551e9322e1393b74286f4a3d4aa387f7bfbaccc1495a98603b"}, + {file = "Cython-0.29.28-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:5e82f6b3dc2133b2e0e2c5c63d352d40a695e40cc7ed99f4cbe83334bcf9ab39"}, + {file = "Cython-0.29.28-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_24_i686.whl", hash = "sha256:49076747b731ed78acf203666c3b3c5d664754ea01ca4527f62f6d8675703688"}, + {file = "Cython-0.29.28-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9f2b7c86a73db0d8dbbd885fe67f04c7b787df37a3848b9867270d3484101fbd"}, + {file = "Cython-0.29.28-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:a3b27812ac9e9737026bfbb1dd47434f3e84013f430bafe1c6cbaf1cd51b5518"}, + {file = "Cython-0.29.28-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:0378a14d2580dcea234d7a2dc8d75f60c091105885096e6dd5b032be97542c16"}, + {file = "Cython-0.29.28-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:d7c98727397c2547a56aa0c3c98140f1873c69a0642edc9446c6c870d0d8a5b5"}, + {file = "Cython-0.29.28-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:6626f9691ce2093ccbcc9932f449efe3b6e1c893b556910881d177c61612e8ff"}, + {file = "Cython-0.29.28-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_24_i686.whl", hash = "sha256:e9cc6af0c9c477c5e175e807dce439509934efefc24ea2da9fced7fbc8170591"}, + {file = "Cython-0.29.28-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:05edfa51c0ff31a8df3cb291b90ca93ab499686d023b9b81c216cd3509f73def"}, + {file = "Cython-0.29.28-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:4b3089255b6b1cc69e4b854626a41193e6acae5332263d24707976b3cb8ca644"}, + {file = "Cython-0.29.28-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:03b749e4f0bbf631cee472add2806d338a7d496f8383f6fb28cc5fdc34b7fdb8"}, + {file = "Cython-0.29.28-py2.py3-none-any.whl", hash = "sha256:26d8d0ededca42be50e0ac377c08408e18802b1391caa3aea045a72c1bff47ac"}, + {file = "Cython-0.29.28.tar.gz", hash = "sha256:d6fac2342802c30e51426828fe084ff4deb1b3387367cf98976bb2e64b6f8e45"}, +] filetype = [ {file = "filetype-1.0.10-py2.py3-none-any.whl", hash = "sha256:63fbe6e818a3d1cfac1d62b196574a7a4b7fc8e06a6c500d53577c018ef127d9"}, {file = "filetype-1.0.10.tar.gz", hash = "sha256:323a13500731b6c65a253bc3930bbce9a56dfba71e90b60ffd968ab69d9ae937"}, ] +greenlet = [ + {file = "greenlet-1.1.2-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:58df5c2a0e293bf665a51f8a100d3e9956febfbf1d9aaf8c0677cf70218910c6"}, + {file = "greenlet-1.1.2-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:aec52725173bd3a7b56fe91bc56eccb26fbdff1386ef123abb63c84c5b43b63a"}, + {file = "greenlet-1.1.2-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:833e1551925ed51e6b44c800e71e77dacd7e49181fdc9ac9a0bf3714d515785d"}, + {file = "greenlet-1.1.2-cp27-cp27m-win32.whl", hash = "sha256:aa5b467f15e78b82257319aebc78dd2915e4c1436c3c0d1ad6f53e47ba6e2713"}, + {file = "greenlet-1.1.2-cp27-cp27m-win_amd64.whl", hash = "sha256:40b951f601af999a8bf2ce8c71e8aaa4e8c6f78ff8afae7b808aae2dc50d4c40"}, + {file = "greenlet-1.1.2-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:95e69877983ea39b7303570fa6760f81a3eec23d0e3ab2021b7144b94d06202d"}, + {file = "greenlet-1.1.2-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:356b3576ad078c89a6107caa9c50cc14e98e3a6c4874a37c3e0273e4baf33de8"}, + {file = "greenlet-1.1.2-cp310-cp310-macosx_10_14_x86_64.whl", hash = "sha256:8639cadfda96737427330a094476d4c7a56ac03de7265622fcf4cfe57c8ae18d"}, + {file = "greenlet-1.1.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:97e5306482182170ade15c4b0d8386ded995a07d7cc2ca8f27958d34d6736497"}, + {file = "greenlet-1.1.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e6a36bb9474218c7a5b27ae476035497a6990e21d04c279884eb10d9b290f1b1"}, + {file = "greenlet-1.1.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:abb7a75ed8b968f3061327c433a0fbd17b729947b400747c334a9c29a9af6c58"}, + {file = "greenlet-1.1.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b336501a05e13b616ef81ce329c0e09ac5ed8c732d9ba7e3e983fcc1a9e86965"}, + {file = "greenlet-1.1.2-cp310-cp310-win_amd64.whl", hash = "sha256:14d4f3cd4e8b524ae9b8aa567858beed70c392fdec26dbdb0a8a418392e71708"}, + {file = "greenlet-1.1.2-cp35-cp35m-macosx_10_14_x86_64.whl", hash = "sha256:17ff94e7a83aa8671a25bf5b59326ec26da379ace2ebc4411d690d80a7fbcf23"}, + {file = "greenlet-1.1.2-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:9f3cba480d3deb69f6ee2c1825060177a22c7826431458c697df88e6aeb3caee"}, + {file = "greenlet-1.1.2-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:fa877ca7f6b48054f847b61d6fa7bed5cebb663ebc55e018fda12db09dcc664c"}, + {file = "greenlet-1.1.2-cp35-cp35m-win32.whl", hash = "sha256:7cbd7574ce8e138bda9df4efc6bf2ab8572c9aff640d8ecfece1b006b68da963"}, + {file = "greenlet-1.1.2-cp35-cp35m-win_amd64.whl", hash = "sha256:903bbd302a2378f984aef528f76d4c9b1748f318fe1294961c072bdc7f2ffa3e"}, + {file = "greenlet-1.1.2-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:049fe7579230e44daef03a259faa24511d10ebfa44f69411d99e6a184fe68073"}, + {file = "greenlet-1.1.2-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:dd0b1e9e891f69e7675ba5c92e28b90eaa045f6ab134ffe70b52e948aa175b3c"}, + {file = "greenlet-1.1.2-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:7418b6bfc7fe3331541b84bb2141c9baf1ec7132a7ecd9f375912eca810e714e"}, + {file = "greenlet-1.1.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f9d29ca8a77117315101425ec7ec2a47a22ccf59f5593378fc4077ac5b754fce"}, + {file = "greenlet-1.1.2-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:21915eb821a6b3d9d8eefdaf57d6c345b970ad722f856cd71739493ce003ad08"}, + {file = "greenlet-1.1.2-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eff9d20417ff9dcb0d25e2defc2574d10b491bf2e693b4e491914738b7908168"}, + {file = "greenlet-1.1.2-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:b8c008de9d0daba7b6666aa5bbfdc23dcd78cafc33997c9b7741ff6353bafb7f"}, + {file = "greenlet-1.1.2-cp36-cp36m-win32.whl", hash = "sha256:32ca72bbc673adbcfecb935bb3fb1b74e663d10a4b241aaa2f5a75fe1d1f90aa"}, + {file = "greenlet-1.1.2-cp36-cp36m-win_amd64.whl", hash = "sha256:f0214eb2a23b85528310dad848ad2ac58e735612929c8072f6093f3585fd342d"}, + {file = "greenlet-1.1.2-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:b92e29e58bef6d9cfd340c72b04d74c4b4e9f70c9fa7c78b674d1fec18896dc4"}, + {file = "greenlet-1.1.2-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:fdcec0b8399108577ec290f55551d926d9a1fa6cad45882093a7a07ac5ec147b"}, + {file = "greenlet-1.1.2-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:93f81b134a165cc17123626ab8da2e30c0455441d4ab5576eed73a64c025b25c"}, + {file = "greenlet-1.1.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1e12bdc622676ce47ae9abbf455c189e442afdde8818d9da983085df6312e7a1"}, + {file = "greenlet-1.1.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8c790abda465726cfb8bb08bd4ca9a5d0a7bd77c7ac1ca1b839ad823b948ea28"}, + {file = "greenlet-1.1.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f276df9830dba7a333544bd41070e8175762a7ac20350786b322b714b0e654f5"}, + {file = "greenlet-1.1.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8c5d5b35f789a030ebb95bff352f1d27a93d81069f2adb3182d99882e095cefe"}, + {file = "greenlet-1.1.2-cp37-cp37m-win32.whl", hash = "sha256:64e6175c2e53195278d7388c454e0b30997573f3f4bd63697f88d855f7a6a1fc"}, + {file = "greenlet-1.1.2-cp37-cp37m-win_amd64.whl", hash = "sha256:b11548073a2213d950c3f671aa88e6f83cda6e2fb97a8b6317b1b5b33d850e06"}, + {file = "greenlet-1.1.2-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:9633b3034d3d901f0a46b7939f8c4d64427dfba6bbc5a36b1a67364cf148a1b0"}, + {file = "greenlet-1.1.2-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:eb6ea6da4c787111adf40f697b4e58732ee0942b5d3bd8f435277643329ba627"}, + {file = "greenlet-1.1.2-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:f3acda1924472472ddd60c29e5b9db0cec629fbe3c5c5accb74d6d6d14773478"}, + {file = "greenlet-1.1.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e859fcb4cbe93504ea18008d1df98dee4f7766db66c435e4882ab35cf70cac43"}, + {file = "greenlet-1.1.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:00e44c8afdbe5467e4f7b5851be223be68adb4272f44696ee71fe46b7036a711"}, + {file = "greenlet-1.1.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec8c433b3ab0419100bd45b47c9c8551248a5aee30ca5e9d399a0b57ac04651b"}, + {file = "greenlet-1.1.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2bde6792f313f4e918caabc46532aa64aa27a0db05d75b20edfc5c6f46479de2"}, + {file = "greenlet-1.1.2-cp38-cp38-win32.whl", hash = "sha256:288c6a76705dc54fba69fbcb59904ae4ad768b4c768839b8ca5fdadec6dd8cfd"}, + {file = "greenlet-1.1.2-cp38-cp38-win_amd64.whl", hash = "sha256:8d2f1fb53a421b410751887eb4ff21386d119ef9cde3797bf5e7ed49fb51a3b3"}, + {file = "greenlet-1.1.2-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:166eac03e48784a6a6e0e5f041cfebb1ab400b394db188c48b3a84737f505b67"}, + {file = "greenlet-1.1.2-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:572e1787d1460da79590bf44304abbc0a2da944ea64ec549188fa84d89bba7ab"}, + {file = "greenlet-1.1.2-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:be5f425ff1f5f4b3c1e33ad64ab994eed12fc284a6ea71c5243fd564502ecbe5"}, + {file = "greenlet-1.1.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b1692f7d6bc45e3200844be0dba153612103db241691088626a33ff1f24a0d88"}, + {file = "greenlet-1.1.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7227b47e73dedaa513cdebb98469705ef0d66eb5a1250144468e9c3097d6b59b"}, + {file = "greenlet-1.1.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ff61ff178250f9bb3cd89752df0f1dd0e27316a8bd1465351652b1b4a4cdfd3"}, + {file = "greenlet-1.1.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:0051c6f1f27cb756ffc0ffbac7d2cd48cb0362ac1736871399a739b2885134d3"}, + {file = "greenlet-1.1.2-cp39-cp39-win32.whl", hash = "sha256:f70a9e237bb792c7cc7e44c531fd48f5897961701cdaa06cf22fc14965c496cf"}, + {file = "greenlet-1.1.2-cp39-cp39-win_amd64.whl", hash = "sha256:013d61294b6cd8fe3242932c1c5e36e5d1db2c8afb58606c5a67efce62c1f5fd"}, + {file = "greenlet-1.1.2.tar.gz", hash = "sha256:e30f5ea4ae2346e62cedde8794a56858a67b878dd79f7df76a0767e356b1744a"}, +] idna = [ {file = "idna-3.3-py3-none-any.whl", hash = "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff"}, {file = "idna-3.3.tar.gz", hash = "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d"}, @@ -1075,6 +1237,44 @@ six = [ {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, ] +sqlalchemy = [ + {file = "SQLAlchemy-1.4.36-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:81e53bd383c2c33de9d578bfcc243f559bd3801a0e57f2bcc9a943c790662e0c"}, + {file = "SQLAlchemy-1.4.36-cp27-cp27m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:6e1fe00ee85c768807f2a139b83469c1e52a9ffd58a6eb51aa7aeb524325ab18"}, + {file = "SQLAlchemy-1.4.36-cp27-cp27m-win32.whl", hash = "sha256:d57ac32f8dc731fddeb6f5d1358b4ca5456e72594e664769f0a9163f13df2a31"}, + {file = "SQLAlchemy-1.4.36-cp27-cp27m-win_amd64.whl", hash = "sha256:fca8322e04b2dde722fcb0558682740eebd3bd239bea7a0d0febbc190e99dc15"}, + {file = "SQLAlchemy-1.4.36-cp27-cp27mu-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:53d2d9ee93970c969bc4e3c78b1277d7129554642f6ffea039c282c7dc4577bc"}, + {file = "SQLAlchemy-1.4.36-cp310-cp310-macosx_10_15_x86_64.whl", hash = "sha256:f0394a3acfb8925db178f7728adb38c027ed7e303665b225906bfa8099dc1ce8"}, + {file = "SQLAlchemy-1.4.36-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:09c606d8238feae2f360b8742ffbe67741937eb0a05b57f536948d198a3def96"}, + {file = "SQLAlchemy-1.4.36-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:8d07fe2de0325d06e7e73281e9a9b5e259fbd7cbfbe398a0433cbb0082ad8fa7"}, + {file = "SQLAlchemy-1.4.36-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5041474dcab7973baa91ec1f3112049a9dd4652898d6a95a6a895ff5c58beb6b"}, + {file = "SQLAlchemy-1.4.36-cp310-cp310-win32.whl", hash = "sha256:be094460930087e50fd08297db9d7aadaed8408ad896baf758e9190c335632da"}, + {file = "SQLAlchemy-1.4.36-cp310-cp310-win_amd64.whl", hash = "sha256:64d796e9af522162f7f2bf7a3c5531a0a550764c426782797bbeed809d0646c5"}, + {file = "SQLAlchemy-1.4.36-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:a0ae3aa2e86a4613f2d4c49eb7da23da536e6ce80b2bfd60bbb2f55fc02b0b32"}, + {file = "SQLAlchemy-1.4.36-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d50cb71c1dbed70646d521a0975fb0f92b7c3f84c61fa59e07be23a1aaeecfc"}, + {file = "SQLAlchemy-1.4.36-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:16abf35af37a3d5af92725fc9ec507dd9e9183d261c2069b6606d60981ed1c6e"}, + {file = "SQLAlchemy-1.4.36-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5864a83bd345871ad9699ce466388f836db7572003d67d9392a71998092210e3"}, + {file = "SQLAlchemy-1.4.36-cp36-cp36m-win32.whl", hash = "sha256:fbf8c09fe9728168f8cc1b40c239eab10baf9c422c18be7f53213d70434dea43"}, + {file = "SQLAlchemy-1.4.36-cp36-cp36m-win_amd64.whl", hash = "sha256:6e859fa96605027bd50d8e966db1c4e1b03e7b3267abbc4b89ae658c99393c58"}, + {file = "SQLAlchemy-1.4.36-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:166a3887ec355f7d2f12738f7fa25dc8ac541867147a255f790f2f41f614cb44"}, + {file = "SQLAlchemy-1.4.36-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e885548da361aa3f8a9433db4cfb335b2107e533bf314359ae3952821d84b3e"}, + {file = "SQLAlchemy-1.4.36-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:5c90ef955d429966d84326d772eb34333178737ebb669845f1d529eb00c75e72"}, + {file = "SQLAlchemy-1.4.36-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a052bd9f53004f8993c624c452dfad8ec600f572dd0ed0445fbe64b22f5570e"}, + {file = "SQLAlchemy-1.4.36-cp37-cp37m-win32.whl", hash = "sha256:dce3468bf1fc12374a1a732c9efd146ce034f91bb0482b602a9311cb6166a920"}, + {file = "SQLAlchemy-1.4.36-cp37-cp37m-win_amd64.whl", hash = "sha256:6cb4c4f57a20710cea277edf720d249d514e587f796b75785ad2c25e1c0fed26"}, + {file = "SQLAlchemy-1.4.36-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:e74ce103b81c375c3853b436297952ef8d7863d801dcffb6728d01544e5191b5"}, + {file = "SQLAlchemy-1.4.36-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b20c4178ead9bc398be479428568ff31b6c296eb22e75776273781a6551973f"}, + {file = "SQLAlchemy-1.4.36-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:af2587ae11400157753115612d6c6ad255143efba791406ad8a0cbcccf2edcb3"}, + {file = "SQLAlchemy-1.4.36-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:83cf3077712be9f65c9aaa0b5bc47bc1a44789fd45053e2e3ecd59ff17c63fe9"}, + {file = "SQLAlchemy-1.4.36-cp38-cp38-win32.whl", hash = "sha256:ce20f5da141f8af26c123ebaa1b7771835ca6c161225ce728962a79054f528c3"}, + {file = "SQLAlchemy-1.4.36-cp38-cp38-win_amd64.whl", hash = "sha256:316c7e5304dda3e3ad711569ac5d02698bbc71299b168ac56a7076b86259f7ea"}, + {file = "SQLAlchemy-1.4.36-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:f522214f6749bc073262529c056f7dfd660f3b5ec4180c5354d985eb7219801e"}, + {file = "SQLAlchemy-1.4.36-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2ecac4db8c1aa4a269f5829df7e706639a24b780d2ac46b3e485cbbd27ec0028"}, + {file = "SQLAlchemy-1.4.36-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:b3db741beaa983d4cbf9087558620e7787106319f7e63a066990a70657dd6b35"}, + {file = "SQLAlchemy-1.4.36-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ec89bf98cc6a0f5d1e28e3ad28e9be6f3b4bdbd521a4053c7ae8d5e1289a8a1"}, + {file = "SQLAlchemy-1.4.36-cp39-cp39-win32.whl", hash = "sha256:e12532c4d3f614678623da5d852f038ace1f01869b89f003ed6fe8c793f0c6a3"}, + {file = "SQLAlchemy-1.4.36-cp39-cp39-win_amd64.whl", hash = "sha256:cb441ca461bf97d00877b607f132772644b623518b39ced54da433215adce691"}, + {file = "SQLAlchemy-1.4.36.tar.gz", hash = "sha256:64678ac321d64a45901ef2e24725ec5e783f1f4a588305e196431447e7ace243"}, +] thriftpy2 = [ {file = "thriftpy2-0.4.14-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:b4aae6f6c1d8d12e63c45f68ec1a25267e7d3af1ced1e5a82cbabaaed4bcebc9"}, {file = "thriftpy2-0.4.14.tar.gz", hash = "sha256:1758ccaeb2a40d8779b50cdd3d7a3b43e8c5752f21ad0a54ded7c251d05219e8"}, diff --git a/pyproject.toml b/pyproject.toml index 55761635..75f37f82 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -45,6 +45,8 @@ filetype = {version = "^1.0.7", optional = true} Brotli = "^1.0.9" jinja2 = "^3.0.3" toml = "^0.10.2" +sqlalchemy = "^1.4.36" +cython = "^0.29.28" thriftpy2 = "^0.4.14" [tool.poetry.extras] From 41ca0f8833499144d1f6e02e17b7d854fa7e8d5a Mon Sep 17 00:00:00 2001 From: "duanchao.bill" <duanchao.bill@bytedance.com> Date: Wed, 4 May 2022 11:11:13 +0800 Subject: [PATCH 009/109] fix: RunThriftRequest --- httprunner/step_thrift_request.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/httprunner/step_thrift_request.py b/httprunner/step_thrift_request.py index fe569032..7eef3e07 100644 --- a/httprunner/step_thrift_request.py +++ b/httprunner/step_thrift_request.py @@ -189,7 +189,7 @@ class RunThriftRequest(IStep): return self - def setup_hook(self, hook: Text, assign_var_name: Text = None) -> "RunTestCase": + def setup_hook(self, hook: Text, assign_var_name: Text = None) -> "RunThriftRequest": if assign_var_name: self.__step.setup_hooks.append({assign_var_name: hook}) else: From ba069e3d2d5f83fa10afb811a7e204e69416f662 Mon Sep 17 00:00:00 2001 From: "duanchao.bill" <duanchao.bill@bytedance.com> Date: Wed, 4 May 2022 11:51:26 +0800 Subject: [PATCH 010/109] fix: thrift --- poetry.lock | 28 ++++++++++++++++++++++++++-- pyproject.toml | 1 + 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/poetry.lock b/poetry.lock index 8c1ec1e6..2b202e54 100644 --- a/poetry.lock +++ b/poetry.lock @@ -623,7 +623,7 @@ name = "six" version = "1.16.0" description = "Python 2 and 3 compatibility utilities" category = "main" -optional = true +optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" [package.source] @@ -669,6 +669,27 @@ type = "legacy" url = "https://pypi.tuna.tsinghua.edu.cn/simple" reference = "tsinghua" +[[package]] +name = "thrift" +version = "0.16.0" +description = "Python bindings for the Apache Thrift RPC system" +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +six = ">=1.7.2" + +[package.extras] +all = ["tornado (>=4.0)", "twisted"] +tornado = ["tornado (>=4.0)"] +twisted = ["twisted"] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + [[package]] name = "thriftpy2" version = "0.4.14" @@ -799,7 +820,7 @@ upload = ["requests-toolbelt", "filetype"] [metadata] lock-version = "1.1" python-versions = "^3.7" -content-hash = "73bf6bfc1612775dd37dd94bb73b73d6de7a38bb3dad7c027c24f7d3d0209e62" +content-hash = "15f15916b41e180ee7567716713607183f5d8c8d30300c24dada98be454bc675" [metadata.files] allure-pytest = [ @@ -1275,6 +1296,9 @@ sqlalchemy = [ {file = "SQLAlchemy-1.4.36-cp39-cp39-win_amd64.whl", hash = "sha256:cb441ca461bf97d00877b607f132772644b623518b39ced54da433215adce691"}, {file = "SQLAlchemy-1.4.36.tar.gz", hash = "sha256:64678ac321d64a45901ef2e24725ec5e783f1f4a588305e196431447e7ace243"}, ] +thrift = [ + {file = "thrift-0.16.0.tar.gz", hash = "sha256:2b5b6488fcded21f9d312aa23c9ff6a0195d0f6ae26ddbd5ad9e3e25dfc14408"}, +] thriftpy2 = [ {file = "thriftpy2-0.4.14-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:b4aae6f6c1d8d12e63c45f68ec1a25267e7d3af1ced1e5a82cbabaaed4bcebc9"}, {file = "thriftpy2-0.4.14.tar.gz", hash = "sha256:1758ccaeb2a40d8779b50cdd3d7a3b43e8c5752f21ad0a54ded7c251d05219e8"}, diff --git a/pyproject.toml b/pyproject.toml index 75f37f82..eaa1d97b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -48,6 +48,7 @@ toml = "^0.10.2" sqlalchemy = "^1.4.36" cython = "^0.29.28" thriftpy2 = "^0.4.14" +thrift = "^0.16.0" [tool.poetry.extras] allure = ["allure-pytest"] # pip install "httprunner[allure]", poetry install -E allure From 1de270f089c994514b052f092ae7de538ffcdbef Mon Sep 17 00:00:00 2001 From: "duanchao.bill" <duanchao.bill@bytedance.com> Date: Wed, 4 May 2022 14:23:09 +0800 Subject: [PATCH 011/109] fix: windows not support thrift --- httprunner/__init__.py | 23 ++++++++++++++-------- httprunner/step.py | 32 ++++++++++++++++++++++++++----- httprunner/step_thrift_request.py | 4 +++- 3 files changed, 45 insertions(+), 14 deletions(-) diff --git a/httprunner/__init__.py b/httprunner/__init__.py index 6fbfbe8a..e70b5526 100644 --- a/httprunner/__init__.py +++ b/httprunner/__init__.py @@ -2,6 +2,7 @@ __version__ = "4.0.0-beta" __description__ = "One-stop solution for HTTP(S) testing." from httprunner.config import Config +import platform from httprunner.parser import parse_parameters as Parameters from httprunner.runner import HttpRunner from httprunner.step import Step @@ -12,11 +13,6 @@ from httprunner.step_sql_request import ( StepSqlRequestValidation, StepSqlRequestExtraction, ) -from httprunner.step_thrift_request import ( - RunThriftRequest, - StepThriftRequestValidation, - StepThriftRequestExtraction, -) __all__ = [ "__version__", @@ -28,9 +24,20 @@ __all__ = [ "RunSqlRequest", "StepSqlRequestValidation", "StepSqlRequestExtraction", - "RunThriftRequest", - "StepThriftRequestValidation", - "StepThriftRequestExtraction", "RunTestCase", "Parameters", ] +if platform.system() != "Windows": + from httprunner.step_thrift_request import ( + RunThriftRequest, + StepThriftRequestValidation, + StepThriftRequestExtraction, + ) + + __all__.extend( + [ + "RunThriftRequest", + "StepThriftRequestValidation", + "StepThriftRequestExtraction", + ] + ) diff --git a/httprunner/step.py b/httprunner/step.py index 75cc43dc..b0d20a9b 100644 --- a/httprunner/step.py +++ b/httprunner/step.py @@ -1,3 +1,4 @@ +import platform from typing import Union from httprunner.models import StepResult, TRequest, TStep, TestCase @@ -14,8 +15,6 @@ from httprunner.step_sql_request import ( StepSqlRequestExtraction, ) -from httprunner.step_thrift_request import RunThriftRequest,StepThriftRequestValidation,StepThriftRequestExtraction - class Step(object): def __init__( @@ -28,9 +27,6 @@ class Step(object): RunSqlRequest, StepSqlRequestValidation, StepSqlRequestExtraction, - RunThriftRequest, - StepThriftRequestValidation, - StepThriftRequestExtraction ], ): self.__step = step @@ -62,3 +58,29 @@ class Step(object): def run(self, runner: HttpRunner) -> StepResult: return self.__step.run(runner) + + +if platform.system() != "Windows": + from httprunner.step_thrift_request import ( + RunThriftRequest, + StepThriftRequestValidation, + StepThriftRequestExtraction, + ) + + class Step(Step): + def __init__( + self, + step: Union[ + StepRequestValidation, + StepRequestExtraction, + RequestWithOptionalArgs, + StepRefCase, + RunSqlRequest, + StepSqlRequestValidation, + StepSqlRequestExtraction, + RunThriftRequest, + StepThriftRequestValidation, + StepThriftRequestExtraction, + ], + ): + super().__init__(step) diff --git a/httprunner/step_thrift_request.py b/httprunner/step_thrift_request.py index 7eef3e07..919df130 100644 --- a/httprunner/step_thrift_request.py +++ b/httprunner/step_thrift_request.py @@ -189,7 +189,9 @@ class RunThriftRequest(IStep): return self - def setup_hook(self, hook: Text, assign_var_name: Text = None) -> "RunThriftRequest": + def setup_hook( + self, hook: Text, assign_var_name: Text = None + ) -> "RunThriftRequest": if assign_var_name: self.__step.setup_hooks.append({assign_var_name: hook}) else: From 5bdbcdc2e510f17a465be74bd734c836cf10091d Mon Sep 17 00:00:00 2001 From: debugtalk <mail@debugtalk.com> Date: Tue, 3 May 2022 23:37:23 +0800 Subject: [PATCH 012/109] feat: stat HTTP request latencies (DNSLookup, TCP Connection and so on) --- docs/CHANGELOG.md | 3 +- docs/cmd/hrp.md | 2 +- docs/cmd/hrp_boom.md | 2 +- docs/cmd/hrp_convert.md | 2 +- docs/cmd/hrp_har2case.md | 2 +- docs/cmd/hrp_pytest.md | 2 +- docs/cmd/hrp_run.md | 3 +- docs/cmd/hrp_startproject.md | 2 +- hrp/cmd/run.go | 5 + hrp/internal/httpstat/main.go | 169 ++++++++++++++++++++++++++++++++++ hrp/runner.go | 8 ++ hrp/session.go | 4 + hrp/step.go | 19 ++-- hrp/step_request.go | 16 ++++ 14 files changed, 223 insertions(+), 16 deletions(-) create mode 100644 hrp/internal/httpstat/main.go diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 51fb4192..e67c879e 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -1,9 +1,10 @@ # Release History -## v4.0.0 (2022-05-03) +## v4.0.0 (2022-05-04) **go version** +- feat: stat HTTP request latencies (DNSLookup, TCP Connection and so on) - feat: add builtin function `environ`/`ENV` - fix: demo function compatibility - fix #1240: losing host port in har2case diff --git a/docs/cmd/hrp.md b/docs/cmd/hrp.md index ef528f9d..ce94ba75 100644 --- a/docs/cmd/hrp.md +++ b/docs/cmd/hrp.md @@ -36,4 +36,4 @@ Copyright 2017 debugtalk * [hrp run](hrp_run.md) - run API test with go engine * [hrp startproject](hrp_startproject.md) - create a scaffold project -###### Auto generated by spf13/cobra on 3-May-2022 +###### Auto generated by spf13/cobra on 4-May-2022 diff --git a/docs/cmd/hrp_boom.md b/docs/cmd/hrp_boom.md index fe95ddab..4344710a 100644 --- a/docs/cmd/hrp_boom.md +++ b/docs/cmd/hrp_boom.md @@ -41,4 +41,4 @@ hrp boom [flags] * [hrp](hrp.md) - Next-Generation API Testing Solution. -###### Auto generated by spf13/cobra on 3-May-2022 +###### Auto generated by spf13/cobra on 4-May-2022 diff --git a/docs/cmd/hrp_convert.md b/docs/cmd/hrp_convert.md index 880bce0b..2010b694 100644 --- a/docs/cmd/hrp_convert.md +++ b/docs/cmd/hrp_convert.md @@ -18,4 +18,4 @@ hrp convert $path... [flags] * [hrp](hrp.md) - Next-Generation API Testing Solution. -###### Auto generated by spf13/cobra on 3-May-2022 +###### Auto generated by spf13/cobra on 4-May-2022 diff --git a/docs/cmd/hrp_har2case.md b/docs/cmd/hrp_har2case.md index d7b6e925..3fd38c9d 100644 --- a/docs/cmd/hrp_har2case.md +++ b/docs/cmd/hrp_har2case.md @@ -24,4 +24,4 @@ hrp har2case $har_path... [flags] * [hrp](hrp.md) - Next-Generation API Testing Solution. -###### Auto generated by spf13/cobra on 3-May-2022 +###### Auto generated by spf13/cobra on 4-May-2022 diff --git a/docs/cmd/hrp_pytest.md b/docs/cmd/hrp_pytest.md index 5c59d217..29678585 100644 --- a/docs/cmd/hrp_pytest.md +++ b/docs/cmd/hrp_pytest.md @@ -16,4 +16,4 @@ hrp pytest $path ... [flags] * [hrp](hrp.md) - Next-Generation API Testing Solution. -###### Auto generated by spf13/cobra on 3-May-2022 +###### Auto generated by spf13/cobra on 4-May-2022 diff --git a/docs/cmd/hrp_run.md b/docs/cmd/hrp_run.md index 6d2f8486..86c7018d 100644 --- a/docs/cmd/hrp_run.md +++ b/docs/cmd/hrp_run.md @@ -24,6 +24,7 @@ hrp run $path... [flags] -c, --continue-on-failure continue running next step when failure occurs -g, --gen-html-report generate html report -h, --help help for run + --http-stat turn on HTTP latency stat (DNSLookup, TCP Connection, etc.) --log-plugin turn on plugin logging --log-requests-off turn off request & response details logging -p, --proxy-url string set proxy url @@ -34,4 +35,4 @@ hrp run $path... [flags] * [hrp](hrp.md) - Next-Generation API Testing Solution. -###### Auto generated by spf13/cobra on 3-May-2022 +###### Auto generated by spf13/cobra on 4-May-2022 diff --git a/docs/cmd/hrp_startproject.md b/docs/cmd/hrp_startproject.md index a8706619..95fa28e6 100644 --- a/docs/cmd/hrp_startproject.md +++ b/docs/cmd/hrp_startproject.md @@ -20,4 +20,4 @@ hrp startproject $project_name [flags] * [hrp](hrp.md) - Next-Generation API Testing Solution. -###### Auto generated by spf13/cobra on 3-May-2022 +###### Auto generated by spf13/cobra on 4-May-2022 diff --git a/hrp/cmd/run.go b/hrp/cmd/run.go index 17f54014..891563fd 100644 --- a/hrp/cmd/run.go +++ b/hrp/cmd/run.go @@ -35,6 +35,9 @@ var runCmd = &cobra.Command{ if !requestsLogOff { runner.SetRequestsLogOn() } + if httpStatOn { + runner.SetHTTPStatOn() + } if pluginLogOn { runner.SetPluginLogOn() } @@ -51,6 +54,7 @@ var runCmd = &cobra.Command{ var ( continueOnFailure bool requestsLogOff bool + httpStatOn bool pluginLogOn bool proxyUrl string saveTests bool @@ -61,6 +65,7 @@ func init() { rootCmd.AddCommand(runCmd) runCmd.Flags().BoolVarP(&continueOnFailure, "continue-on-failure", "c", false, "continue running next step when failure occurs") runCmd.Flags().BoolVar(&requestsLogOff, "log-requests-off", false, "turn off request & response details logging") + runCmd.Flags().BoolVar(&httpStatOn, "http-stat", false, "turn on HTTP latency stat (DNSLookup, TCP Connection, etc.)") runCmd.Flags().BoolVar(&pluginLogOn, "log-plugin", false, "turn on plugin logging") runCmd.Flags().StringVarP(&proxyUrl, "proxy-url", "p", "", "set proxy url") runCmd.Flags().BoolVarP(&saveTests, "save-tests", "s", false, "save tests summary") diff --git a/hrp/internal/httpstat/main.go b/hrp/internal/httpstat/main.go new file mode 100644 index 00000000..1d9a1a5e --- /dev/null +++ b/hrp/internal/httpstat/main.go @@ -0,0 +1,169 @@ +// Package httpstat traces HTTP latency infomation (DNSLookup, TCP Connection and so on) on any golang HTTP request. +// It uses `httptrace` package. +// Forked from https://github.com/tcnksm/go-httpstat +package httpstat + +import ( + "context" + "crypto/tls" + "net/http/httptrace" + "time" +) + +// Stat stores httpstat info. +type Stat struct { + // The following are duration for each phase + // DNSLookup => TCPConnection => TLSHandshake => ServerProcessing => ContentTransfer + DNSLookup time.Duration + TCPConnection time.Duration + TLSHandshake time.Duration + ServerProcessing time.Duration + ContentTransfer time.Duration // from the first response byte to tansfer done. + + // The followings are timeline of request + NameLookup time.Duration // = DNSLookup + Connect time.Duration // = DNSLookup + TCPConnection + Pretransfer time.Duration // = DNSLookup + TCPConnection + TLSHandshake + StartTransfer time.Duration // = DNSLookup + TCPConnection + TLSHandshake + ServerProcessing + Total time.Duration // = DNSLookup + TCPConnection + TLSHandshake + ServerProcessing + ContentTransfer + + // internal timelines, including start and finish timestamps of each phase + dnsStart time.Time + dnsDone time.Time + tcpStart time.Time + tcpDone time.Time + tlsStart time.Time + tlsDone time.Time + serverStart time.Time + serverDone time.Time + transferStart time.Time + transferDone time.Time // need to be provided from outside + + // isTLS is true when connection seems to use TLS + isTLS bool + + // isReused is true when connection is reused (keep-alive) + isReused bool +} + +// Finish sets the time when reading response is done. +// This must be called after reading response body. +func (s *Stat) Finish() { + s.transferDone = time.Now() + + // This means result is empty (it does nothing). + // Skip setting value (contentTransfer and total will be zero). + if s.dnsStart.IsZero() { + return + } + + s.ContentTransfer = s.transferDone.Sub(s.transferStart) + s.Total = s.transferDone.Sub(s.dnsStart) +} + +// Durations returns all durations and timelines of request latencies +func (s *Stat) Durations() map[string]time.Duration { + return map[string]time.Duration{ + "DNSLookup": s.DNSLookup / time.Millisecond, + "TCPConnection": s.TCPConnection / time.Millisecond, + "TLSHandshake": s.TLSHandshake / time.Millisecond, + "ServerProcessing": s.ServerProcessing / time.Millisecond, + "ContentTransfer": s.ContentTransfer / time.Millisecond, + "NameLookup": s.NameLookup / time.Millisecond, + "Connect": s.Connect / time.Millisecond, + "Pretransfer": s.Connect / time.Millisecond, + "StartTransfer": s.StartTransfer / time.Millisecond, + "Total": s.Total / time.Millisecond, + } +} + +// WithHTTPStat is a wrapper of httptrace.WithClientTrace. +// It records the time of each httptrace hooks. +func WithHTTPStat(ctx context.Context, s *Stat) context.Context { + return httptrace.WithClientTrace(ctx, &httptrace.ClientTrace{ + DNSStart: func(i httptrace.DNSStartInfo) { + s.dnsStart = time.Now() + }, + + DNSDone: func(i httptrace.DNSDoneInfo) { + s.dnsDone = time.Now() + + s.DNSLookup = s.dnsDone.Sub(s.dnsStart) + s.NameLookup = s.dnsDone.Sub(s.dnsStart) + }, + + ConnectStart: func(_, _ string) { + s.tcpStart = time.Now() + + // When connecting to IP (When no DNS lookup) + if s.dnsStart.IsZero() { + s.dnsStart = s.tcpStart + s.dnsDone = s.tcpStart + } + }, + + ConnectDone: func(network, addr string, err error) { + s.tcpDone = time.Now() + s.TCPConnection = s.tcpDone.Sub(s.tcpStart) + s.Connect = s.tcpDone.Sub(s.dnsStart) + }, + + TLSHandshakeStart: func() { + s.isTLS = true + s.tlsStart = time.Now() + }, + + TLSHandshakeDone: func(_ tls.ConnectionState, _ error) { + s.tlsDone = time.Now() + s.TLSHandshake = s.tlsDone.Sub(s.tlsStart) + s.Pretransfer = s.tlsDone.Sub(s.dnsStart) + }, + + GotConn: func(i httptrace.GotConnInfo) { + // Handle when keep alive is used and connection is reused. + // DNSStart(Done) and ConnectStart(Done) is skipped + if i.Reused { + s.isReused = true + } + }, + + WroteRequest: func(info httptrace.WroteRequestInfo) { + s.serverStart = time.Now() + + // When client doesn't use DialContext or using old (before go1.7) `net` + // package, DNS/TCP/TLS hook is not called. + if s.dnsStart.IsZero() && s.tcpStart.IsZero() { + now := s.serverStart + s.dnsStart = now + s.dnsDone = now + s.tcpStart = now + s.tcpDone = now + } + + // When connection is re-used, DNS/TCP/TLS hook is not called. + if s.isReused { + now := s.serverStart + s.dnsStart = now + s.dnsDone = now + s.tcpStart = now + s.tcpDone = now + s.tlsStart = now + s.tlsDone = now + } + + if s.isTLS { + return + } + + s.TLSHandshake = s.tcpDone.Sub(s.tcpDone) + s.Pretransfer = s.Connect + }, + + GotFirstResponseByte: func() { + s.serverDone = time.Now() + s.ServerProcessing = s.serverDone.Sub(s.serverStart) + s.StartTransfer = s.serverDone.Sub(s.dnsStart) + s.transferStart = s.serverDone + }, + }) +} diff --git a/hrp/runner.go b/hrp/runner.go index d21b8beb..74c463cd 100644 --- a/hrp/runner.go +++ b/hrp/runner.go @@ -54,6 +54,7 @@ func NewRunner(t *testing.T) *HRPRunner { type HRPRunner struct { t *testing.T failfast bool + httpStatOn bool requestsLogOn bool pluginLogOn bool saveTests bool @@ -100,6 +101,13 @@ func (r *HRPRunner) SetRequestsLogOn() *HRPRunner { return r } +// SetHTTPStatOn turns on HTTP latency stat. +func (r *HRPRunner) SetHTTPStatOn() *HRPRunner { + log.Info().Msg("[init] SetHTTPStatOn") + r.httpStatOn = true + return r +} + // SetPluginLogOn turns on plugin logging. func (r *HRPRunner) SetPluginLogOn() *HRPRunner { log.Info().Msg("[init] SetPluginLogOn") diff --git a/hrp/session.go b/hrp/session.go index 96710284..eae3b41a 100644 --- a/hrp/session.go +++ b/hrp/session.go @@ -42,6 +42,10 @@ func (r *SessionRunner) GetConfig() *TConfig { return r.parsedConfig } +func (r *SessionRunner) HTTPStatOn() bool { + return r.hrpRunner.httpStatOn +} + func (r *SessionRunner) LogOn() bool { return r.hrpRunner.requestsLogOn } diff --git a/hrp/step.go b/hrp/step.go index 242f9294..4ee91355 100644 --- a/hrp/step.go +++ b/hrp/step.go @@ -1,5 +1,7 @@ package hrp +import "time" + type StepType string const ( @@ -13,14 +15,15 @@ const ( ) type StepResult struct { - Name string `json:"name" yaml:"name"` // step name - StepType StepType `json:"step_type" yaml:"step_type"` // step type, testcase/request/transaction/rendezvous - Success bool `json:"success" yaml:"success"` // step execution result - Elapsed int64 `json:"elapsed_ms" yaml:"elapsed_ms"` // step execution time in millisecond(ms) - Data interface{} `json:"data,omitempty" yaml:"data,omitempty"` // session data or slice of step data - ContentSize int64 `json:"content_size" yaml:"content_size"` // response body length - ExportVars map[string]interface{} `json:"export_vars,omitempty" yaml:"export_vars,omitempty"` // extract variables - Attachment string `json:"attachment,omitempty" yaml:"attachment,omitempty"` // step error information + Name string `json:"name" yaml:"name"` // step name + StepType StepType `json:"step_type" yaml:"step_type"` // step type, testcase/request/transaction/rendezvous + Success bool `json:"success" yaml:"success"` // step execution result + Elapsed int64 `json:"elapsed_ms" yaml:"elapsed_ms"` // step execution time in millisecond(ms) + HttpStat map[string]time.Duration `json:"httpstat" yaml:"httpstat"` // httpstat in millisecond(ms) + Data interface{} `json:"data,omitempty" yaml:"data,omitempty"` // session data or slice of step data + ContentSize int64 `json:"content_size" yaml:"content_size"` // response body length + ExportVars map[string]interface{} `json:"export_vars,omitempty" yaml:"export_vars,omitempty"` // extract variables + Attachment string `json:"attachment,omitempty" yaml:"attachment,omitempty"` // step error information } // TStep represents teststep data structure. diff --git a/hrp/step_request.go b/hrp/step_request.go index 64994657..d1b9e7a8 100644 --- a/hrp/step_request.go +++ b/hrp/step_request.go @@ -19,6 +19,7 @@ import ( "github.com/rs/zerolog/log" "github.com/httprunner/httprunner/hrp/internal/builtin" + "github.com/httprunner/httprunner/hrp/internal/httpstat" "github.com/httprunner/httprunner/hrp/internal/json" ) @@ -311,6 +312,13 @@ func runStepRequest(r *SessionRunner, step *TStep) (stepResult *StepResult, err } } + // stat HTTP request + var httpStat httpstat.Stat + if r.HTTPStatOn() { + ctx := httpstat.WithHTTPStat(rb.req.Context(), &httpStat) + rb.req = rb.req.WithContext(ctx) + } + // do request action start := time.Now() var resp *http.Response @@ -339,6 +347,14 @@ func runStepRequest(r *SessionRunner, step *TStep) (stepResult *StepResult, err } } + if r.HTTPStatOn() { + httpStat.Finish() + stepResult.HttpStat = httpStat.Durations() + log.Info(). + Interface("httpstat(ms)", httpStat.Durations()). + Msg("HTTP latency statistics") + } + // new response object respObj, err := newHttpResponseObject(r.hrpRunner.t, parser, resp) if err != nil { From 9f5736260ca83f9bfb24f9843ec3885475b74b2d Mon Sep 17 00:00:00 2001 From: debugtalk <mail@debugtalk.com> Date: Thu, 5 May 2022 00:29:14 +0800 Subject: [PATCH 013/109] test: add unittest for httpstat --- hrp/internal/httpstat/main.go | 24 +++++----- hrp/step.go | 20 ++++---- hrp/step_request_test.go | 86 +++++++++++++++++++++++++++++++++++ 3 files changed, 107 insertions(+), 23 deletions(-) diff --git a/hrp/internal/httpstat/main.go b/hrp/internal/httpstat/main.go index 1d9a1a5e..765e7171 100644 --- a/hrp/internal/httpstat/main.go +++ b/hrp/internal/httpstat/main.go @@ -62,18 +62,18 @@ func (s *Stat) Finish() { } // Durations returns all durations and timelines of request latencies -func (s *Stat) Durations() map[string]time.Duration { - return map[string]time.Duration{ - "DNSLookup": s.DNSLookup / time.Millisecond, - "TCPConnection": s.TCPConnection / time.Millisecond, - "TLSHandshake": s.TLSHandshake / time.Millisecond, - "ServerProcessing": s.ServerProcessing / time.Millisecond, - "ContentTransfer": s.ContentTransfer / time.Millisecond, - "NameLookup": s.NameLookup / time.Millisecond, - "Connect": s.Connect / time.Millisecond, - "Pretransfer": s.Connect / time.Millisecond, - "StartTransfer": s.StartTransfer / time.Millisecond, - "Total": s.Total / time.Millisecond, +func (s *Stat) Durations() map[string]int64 { + return map[string]int64{ + "DNSLookup": s.DNSLookup.Milliseconds(), + "TCPConnection": s.TCPConnection.Milliseconds(), + "TLSHandshake": s.TLSHandshake.Milliseconds(), + "ServerProcessing": s.ServerProcessing.Milliseconds(), + "ContentTransfer": s.ContentTransfer.Milliseconds(), + "NameLookup": s.NameLookup.Milliseconds(), + "Connect": s.Connect.Milliseconds(), + "Pretransfer": s.Connect.Milliseconds(), + "StartTransfer": s.StartTransfer.Milliseconds(), + "Total": s.Total.Milliseconds(), } } diff --git a/hrp/step.go b/hrp/step.go index 4ee91355..8a9d0031 100644 --- a/hrp/step.go +++ b/hrp/step.go @@ -1,7 +1,5 @@ package hrp -import "time" - type StepType string const ( @@ -15,15 +13,15 @@ const ( ) type StepResult struct { - Name string `json:"name" yaml:"name"` // step name - StepType StepType `json:"step_type" yaml:"step_type"` // step type, testcase/request/transaction/rendezvous - Success bool `json:"success" yaml:"success"` // step execution result - Elapsed int64 `json:"elapsed_ms" yaml:"elapsed_ms"` // step execution time in millisecond(ms) - HttpStat map[string]time.Duration `json:"httpstat" yaml:"httpstat"` // httpstat in millisecond(ms) - Data interface{} `json:"data,omitempty" yaml:"data,omitempty"` // session data or slice of step data - ContentSize int64 `json:"content_size" yaml:"content_size"` // response body length - ExportVars map[string]interface{} `json:"export_vars,omitempty" yaml:"export_vars,omitempty"` // extract variables - Attachment string `json:"attachment,omitempty" yaml:"attachment,omitempty"` // step error information + Name string `json:"name" yaml:"name"` // step name + StepType StepType `json:"step_type" yaml:"step_type"` // step type, testcase/request/transaction/rendezvous + Success bool `json:"success" yaml:"success"` // step execution result + Elapsed int64 `json:"elapsed_ms" yaml:"elapsed_ms"` // step execution time in millisecond(ms) + HttpStat map[string]int64 `json:"httpstat" yaml:"httpstat"` // httpstat in millisecond(ms) + Data interface{} `json:"data,omitempty" yaml:"data,omitempty"` // session data or slice of step data + ContentSize int64 `json:"content_size" yaml:"content_size"` // response body length + ExportVars map[string]interface{} `json:"export_vars,omitempty" yaml:"export_vars,omitempty"` // extract variables + Attachment string `json:"attachment,omitempty" yaml:"attachment,omitempty"` // step error information } // TStep represents teststep data structure. diff --git a/hrp/step_request_test.go b/hrp/step_request_test.go index 234119f4..795595b1 100644 --- a/hrp/step_request_test.go +++ b/hrp/step_request_test.go @@ -2,6 +2,8 @@ package hrp import ( "testing" + + "github.com/stretchr/testify/assert" ) var ( @@ -89,3 +91,87 @@ func TestRunRequestRun(t *testing.T) { t.Fatalf("stepPOSTData.Run() error: %v", err) } } + +func TestRunRequestStatOn(t *testing.T) { + testcase := &TestCase{ + Config: NewConfig("test").SetBaseURL("https://postman-echo.com"), + TestSteps: []IStep{stepGET, stepPOSTData}, + } + runner := NewRunner(t).SetHTTPStatOn() + sessionRunner, _ := runner.NewSessionRunner(testcase) + if err := sessionRunner.Start(nil); err != nil { + t.Fatal() + } + summary := sessionRunner.GetSummary() + + stat := summary.Records[0].HttpStat + if !assert.Greater(t, stat["DNSLookup"], int64(1)) { + t.Fatal() + } + if !assert.Greater(t, stat["TCPConnection"], int64(1)) { + t.Fatal() + } + if !assert.Greater(t, stat["TLSHandshake"], int64(1)) { + t.Fatal() + } + if !assert.Greater(t, stat["ServerProcessing"], int64(1)) { + t.Fatal() + } + if !assert.GreaterOrEqual(t, stat["ContentTransfer"], int64(0)) { + t.Fatal() + } + if !assert.Greater(t, stat["NameLookup"], int64(1)) { + t.Fatal() + } + if !assert.Greater(t, stat["Connect"], int64(1)) { + t.Fatal() + } + if !assert.Greater(t, stat["Pretransfer"], int64(1)) { + t.Fatal() + } + if !assert.Greater(t, stat["StartTransfer"], int64(1)) { + t.Fatal() + } + if !assert.Greater(t, stat["Total"], int64(10)) { + t.Fatal() + } + if !assert.Less(t, stat["Total"]-summary.Records[0].Elapsed, int64(2)) { + t.Fatal() + } + + // reuse connection + stat = summary.Records[1].HttpStat + if !assert.Equal(t, stat["DNSLookup"], int64(0)) { + t.Fatal() + } + if !assert.Equal(t, stat["TCPConnection"], int64(0)) { + t.Fatal() + } + if !assert.Equal(t, stat["TLSHandshake"], int64(0)) { + t.Fatal() + } + if !assert.Greater(t, stat["ServerProcessing"], int64(1)) { + t.Fatal() + } + if !assert.Equal(t, stat["ContentTransfer"], int64(0)) { + t.Fatal() + } + if !assert.Equal(t, stat["NameLookup"], int64(0)) { + t.Fatal() + } + if !assert.Equal(t, stat["Connect"], int64(0)) { + t.Fatal() + } + if !assert.Equal(t, stat["Pretransfer"], int64(0)) { + t.Fatal() + } + if !assert.Greater(t, stat["StartTransfer"], int64(10)) { + t.Fatal() + } + if !assert.Greater(t, stat["Total"], int64(10)) { + t.Fatal() + } + if !assert.Less(t, stat["Total"]-summary.Records[0].Elapsed, int64(2)) { + t.Fatal() + } +} From 0bd8cf39eafc022c43b72dbb3aee8d564ac80239 Mon Sep 17 00:00:00 2001 From: debugtalk <mail@debugtalk.com> Date: Thu, 5 May 2022 14:24:48 +0800 Subject: [PATCH 014/109] feat: print http stat with color --- go.mod | 1 + go.sum | 4 +- hrp/internal/httpstat/main.go | 93 +++++++++++++++++++++++++++++++++-- hrp/step.go | 2 +- hrp/step_request.go | 4 +- hrp/step_request_test.go | 20 ++++---- 6 files changed, 106 insertions(+), 18 deletions(-) diff --git a/go.mod b/go.mod index 510515e6..cdba9350 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.16 require ( github.com/andybalholm/brotli v1.0.4 github.com/denisbrodbeck/machineid v1.0.1 + github.com/fatih/color v1.13.0 github.com/getsentry/sentry-go v0.13.0 github.com/google/uuid v1.3.0 github.com/gorilla/websocket v1.4.1 diff --git a/go.sum b/go.sum index 24c26644..e72fb518 100644 --- a/go.sum +++ b/go.sum @@ -106,8 +106,9 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.m github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/etcd-io/bbolt v1.3.3/go.mod h1:ZF2nL25h33cCyBtcyWeZ2/I3HQOfTP+0PIEvHjkjCrw= -github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= +github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= @@ -303,6 +304,7 @@ github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaO github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.11 h1:nQ+aFkoE2TMGc0b68U2OKSexC+eq46+XwZzWXHRmPYs= github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= diff --git a/hrp/internal/httpstat/main.go b/hrp/internal/httpstat/main.go index 765e7171..8d8ccc0d 100644 --- a/hrp/internal/httpstat/main.go +++ b/hrp/internal/httpstat/main.go @@ -1,15 +1,64 @@ // Package httpstat traces HTTP latency infomation (DNSLookup, TCP Connection and so on) on any golang HTTP request. // It uses `httptrace` package. -// Forked from https://github.com/tcnksm/go-httpstat +// Inspired by https://github.com/tcnksm/go-httpstat and https://github.com/davecheney/httpstat package httpstat import ( "context" "crypto/tls" + "fmt" "net/http/httptrace" + "strconv" + "strings" "time" + + "github.com/fatih/color" + "github.com/rs/zerolog/log" ) +const ( + httpsTemplate = "\n" + + ` DNS Lookup TCP Connection TLS Handshake Server Processing Content Transfer` + "\n" + + `[%s | %s | %s | %s | %s ]` + "\n" + + ` | | | | |` + "\n" + + ` namelookup:%s | | | |` + "\n" + + ` connect:%s | | |` + "\n" + + ` pretransfer:%s | |` + "\n" + + ` starttransfer:%s |` + "\n" + + ` total:%s` + "\n\n" + + httpTemplate = "\n" + + ` DNS Lookup TCP Connection Server Processing Content Transfer` + "\n" + + `[ %s | %s | %s | %s ]` + "\n" + + ` | | | |` + "\n" + + ` namelookup:%s | | |` + "\n" + + ` connect:%s | |` + "\n" + + ` starttransfer:%s |` + "\n" + + ` total:%s` + "\n\n" +) + +func fmta(d time.Duration) string { + return color.BlueString("%7dms", int(d.Milliseconds())) +} + +func fmtb(d time.Duration) string { + return color.MagentaString("%-9s", strconv.Itoa(int(d.Milliseconds()))+"ms") +} + +func grayscale(code color.Attribute) func(string, ...interface{}) string { + return color.New(code + 232).SprintfFunc() +} + +func colorize(s string) string { + v := strings.Split(s, "\n") + v[0] = grayscale(16)(v[0]) + return strings.Join(v, "\n") +} + +func printf(format string, a ...interface{}) (n int, err error) { + return fmt.Fprintf(color.Output, format, a...) +} + // Stat stores httpstat info. type Stat struct { // The following are duration for each phase @@ -61,6 +110,11 @@ func (s *Stat) Finish() { s.Total = s.transferDone.Sub(s.dnsStart) } +func (s *Stat) assertSchemaHTTP() bool { + // HTTP other than HTTPS + return s.dnsStart.IsZero() && s.tcpStart.IsZero() +} + // Durations returns all durations and timelines of request latencies func (s *Stat) Durations() map[string]int64 { return map[string]int64{ @@ -71,12 +125,45 @@ func (s *Stat) Durations() map[string]int64 { "ContentTransfer": s.ContentTransfer.Milliseconds(), "NameLookup": s.NameLookup.Milliseconds(), "Connect": s.Connect.Milliseconds(), - "Pretransfer": s.Connect.Milliseconds(), + "Pretransfer": s.Pretransfer.Milliseconds(), "StartTransfer": s.StartTransfer.Milliseconds(), "Total": s.Total.Milliseconds(), } } +func (s *Stat) Print() { + if s.assertSchemaHTTP() { + // http + printf(colorize(httpTemplate), + fmta(s.DNSLookup), // dns lookup + fmta(s.TCPConnection), // tcp connection + fmta(s.ServerProcessing), // server processing + fmta(s.ContentTransfer), // content transfer + fmtb(s.NameLookup), // namelookup + fmtb(s.Connect), // connect + fmtb(s.StartTransfer), // starttransfer + fmtb(s.Total), // total + ) + } else { + // https + printf(colorize(httpsTemplate), + fmta(s.DNSLookup), // dns lookup + fmta(s.TCPConnection), // tcp connection + fmta(s.TLSHandshake), // tls handshake + fmta(s.ServerProcessing), // server processing + fmta(s.ContentTransfer), // content transfer + fmtb(s.NameLookup), // namelookup + fmtb(s.Connect), // connect + fmtb(s.Pretransfer), // pretransfer + fmtb(s.StartTransfer), // starttransfer + fmtb(s.Total), // total + ) + } + log.Info(). + Interface("httpstat(ms)", s.Durations()). + Msg("HTTP latency statistics") +} + // WithHTTPStat is a wrapper of httptrace.WithClientTrace. // It records the time of each httptrace hooks. func WithHTTPStat(ctx context.Context, s *Stat) context.Context { @@ -132,7 +219,7 @@ func WithHTTPStat(ctx context.Context, s *Stat) context.Context { // When client doesn't use DialContext or using old (before go1.7) `net` // package, DNS/TCP/TLS hook is not called. - if s.dnsStart.IsZero() && s.tcpStart.IsZero() { + if s.assertSchemaHTTP() { now := s.serverStart s.dnsStart = now s.dnsDone = now diff --git a/hrp/step.go b/hrp/step.go index 8a9d0031..b4583852 100644 --- a/hrp/step.go +++ b/hrp/step.go @@ -17,7 +17,7 @@ type StepResult struct { StepType StepType `json:"step_type" yaml:"step_type"` // step type, testcase/request/transaction/rendezvous Success bool `json:"success" yaml:"success"` // step execution result Elapsed int64 `json:"elapsed_ms" yaml:"elapsed_ms"` // step execution time in millisecond(ms) - HttpStat map[string]int64 `json:"httpstat" yaml:"httpstat"` // httpstat in millisecond(ms) + HttpStat map[string]int64 `json:"httpstat,omitempty" yaml:"httpstat,omitempty"` // httpstat in millisecond(ms) Data interface{} `json:"data,omitempty" yaml:"data,omitempty"` // session data or slice of step data ContentSize int64 `json:"content_size" yaml:"content_size"` // response body length ExportVars map[string]interface{} `json:"export_vars,omitempty" yaml:"export_vars,omitempty"` // extract variables diff --git a/hrp/step_request.go b/hrp/step_request.go index d1b9e7a8..9dbae164 100644 --- a/hrp/step_request.go +++ b/hrp/step_request.go @@ -350,9 +350,7 @@ func runStepRequest(r *SessionRunner, step *TStep) (stepResult *StepResult, err if r.HTTPStatOn() { httpStat.Finish() stepResult.HttpStat = httpStat.Durations() - log.Info(). - Interface("httpstat(ms)", httpStat.Durations()). - Msg("HTTP latency statistics") + httpStat.Print() } // new response object diff --git a/hrp/step_request_test.go b/hrp/step_request_test.go index 795595b1..8a88e3bb 100644 --- a/hrp/step_request_test.go +++ b/hrp/step_request_test.go @@ -105,31 +105,31 @@ func TestRunRequestStatOn(t *testing.T) { summary := sessionRunner.GetSummary() stat := summary.Records[0].HttpStat - if !assert.Greater(t, stat["DNSLookup"], int64(1)) { + if !assert.Greater(t, stat["DNSLookup"], int64(0)) { t.Fatal() } - if !assert.Greater(t, stat["TCPConnection"], int64(1)) { + if !assert.Greater(t, stat["TCPConnection"], int64(0)) { t.Fatal() } - if !assert.Greater(t, stat["TLSHandshake"], int64(1)) { + if !assert.Greater(t, stat["TLSHandshake"], int64(0)) { t.Fatal() } - if !assert.Greater(t, stat["ServerProcessing"], int64(1)) { + if !assert.Greater(t, stat["ServerProcessing"], int64(10)) { t.Fatal() } if !assert.GreaterOrEqual(t, stat["ContentTransfer"], int64(0)) { t.Fatal() } - if !assert.Greater(t, stat["NameLookup"], int64(1)) { + if !assert.Greater(t, stat["NameLookup"], int64(0)) { t.Fatal() } - if !assert.Greater(t, stat["Connect"], int64(1)) { + if !assert.Greater(t, stat["Connect"], int64(0)) { t.Fatal() } - if !assert.Greater(t, stat["Pretransfer"], int64(1)) { + if !assert.Greater(t, stat["Pretransfer"], int64(0)) { t.Fatal() } - if !assert.Greater(t, stat["StartTransfer"], int64(1)) { + if !assert.Greater(t, stat["StartTransfer"], int64(0)) { t.Fatal() } if !assert.Greater(t, stat["Total"], int64(10)) { @@ -150,7 +150,7 @@ func TestRunRequestStatOn(t *testing.T) { if !assert.Equal(t, stat["TLSHandshake"], int64(0)) { t.Fatal() } - if !assert.Greater(t, stat["ServerProcessing"], int64(1)) { + if !assert.Greater(t, stat["ServerProcessing"], int64(10)) { t.Fatal() } if !assert.Equal(t, stat["ContentTransfer"], int64(0)) { @@ -165,7 +165,7 @@ func TestRunRequestStatOn(t *testing.T) { if !assert.Equal(t, stat["Pretransfer"], int64(0)) { t.Fatal() } - if !assert.Greater(t, stat["StartTransfer"], int64(10)) { + if !assert.Greater(t, stat["StartTransfer"], int64(0)) { t.Fatal() } if !assert.Greater(t, stat["Total"], int64(10)) { From fb229c89c478e1594453f2d62112d8565463cc4c Mon Sep 17 00:00:00 2001 From: debugtalk <mail@debugtalk.com> Date: Thu, 5 May 2022 14:33:07 +0800 Subject: [PATCH 015/109] change: skip funppy version requirements --- hrp/internal/scaffold/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hrp/internal/scaffold/main.go b/hrp/internal/scaffold/main.go index 98f5ffed..d655384d 100644 --- a/hrp/internal/scaffold/main.go +++ b/hrp/internal/scaffold/main.go @@ -185,7 +185,7 @@ func createPythonPlugin(projectName string) error { return errors.Wrap(err, "copy file failed") } - _, err = builtin.EnsurePython3Venv(fmt.Sprintf("funppy>=%s", shared.Version)) + _, err = builtin.EnsurePython3Venv("funppy") if err != nil { return err } From 1b83b17a14fbf870a875643a232f6805d5938af9 Mon Sep 17 00:00:00 2001 From: debugtalk <mail@debugtalk.com> Date: Thu, 5 May 2022 14:44:20 +0800 Subject: [PATCH 016/109] fix: unittest --- hrp/step_request_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/hrp/step_request_test.go b/hrp/step_request_test.go index 8a88e3bb..254cdee1 100644 --- a/hrp/step_request_test.go +++ b/hrp/step_request_test.go @@ -105,7 +105,7 @@ func TestRunRequestStatOn(t *testing.T) { summary := sessionRunner.GetSummary() stat := summary.Records[0].HttpStat - if !assert.Greater(t, stat["DNSLookup"], int64(0)) { + if !assert.GreaterOrEqual(t, stat["DNSLookup"], int64(0)) { t.Fatal() } if !assert.Greater(t, stat["TCPConnection"], int64(0)) { @@ -114,7 +114,7 @@ func TestRunRequestStatOn(t *testing.T) { if !assert.Greater(t, stat["TLSHandshake"], int64(0)) { t.Fatal() } - if !assert.Greater(t, stat["ServerProcessing"], int64(10)) { + if !assert.Greater(t, stat["ServerProcessing"], int64(1)) { t.Fatal() } if !assert.GreaterOrEqual(t, stat["ContentTransfer"], int64(0)) { @@ -150,7 +150,7 @@ func TestRunRequestStatOn(t *testing.T) { if !assert.Equal(t, stat["TLSHandshake"], int64(0)) { t.Fatal() } - if !assert.Greater(t, stat["ServerProcessing"], int64(10)) { + if !assert.Greater(t, stat["ServerProcessing"], int64(1)) { t.Fatal() } if !assert.Equal(t, stat["ContentTransfer"], int64(0)) { From 8d425aa696e93cbb72ae60e14c16482382caba44 Mon Sep 17 00:00:00 2001 From: debugtalk <mail@debugtalk.com> Date: Thu, 5 May 2022 15:56:02 +0800 Subject: [PATCH 017/109] change: upgrade funplugin to v0.4.5 --- docs/CHANGELOG.md | 2 +- docs/cmd/hrp.md | 2 +- docs/cmd/hrp_boom.md | 2 +- docs/cmd/hrp_convert.md | 2 +- docs/cmd/hrp_har2case.md | 2 +- docs/cmd/hrp_pytest.md | 2 +- docs/cmd/hrp_run.md | 2 +- docs/cmd/hrp_startproject.md | 2 +- examples/demo-with-go-plugin/plugin/go.mod | 2 +- examples/demo-with-go-plugin/plugin/go.sum | 4 ++-- go.mod | 2 +- go.sum | 4 ++-- 12 files changed, 14 insertions(+), 14 deletions(-) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index e67c879e..adee1c8c 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -1,6 +1,6 @@ # Release History -## v4.0.0 (2022-05-04) +## v4.0.0 (2022-05-05) **go version** diff --git a/docs/cmd/hrp.md b/docs/cmd/hrp.md index ce94ba75..1b512dba 100644 --- a/docs/cmd/hrp.md +++ b/docs/cmd/hrp.md @@ -36,4 +36,4 @@ Copyright 2017 debugtalk * [hrp run](hrp_run.md) - run API test with go engine * [hrp startproject](hrp_startproject.md) - create a scaffold project -###### Auto generated by spf13/cobra on 4-May-2022 +###### Auto generated by spf13/cobra on 5-May-2022 diff --git a/docs/cmd/hrp_boom.md b/docs/cmd/hrp_boom.md index 4344710a..dc049340 100644 --- a/docs/cmd/hrp_boom.md +++ b/docs/cmd/hrp_boom.md @@ -41,4 +41,4 @@ hrp boom [flags] * [hrp](hrp.md) - Next-Generation API Testing Solution. -###### Auto generated by spf13/cobra on 4-May-2022 +###### Auto generated by spf13/cobra on 5-May-2022 diff --git a/docs/cmd/hrp_convert.md b/docs/cmd/hrp_convert.md index 2010b694..5366a607 100644 --- a/docs/cmd/hrp_convert.md +++ b/docs/cmd/hrp_convert.md @@ -18,4 +18,4 @@ hrp convert $path... [flags] * [hrp](hrp.md) - Next-Generation API Testing Solution. -###### Auto generated by spf13/cobra on 4-May-2022 +###### Auto generated by spf13/cobra on 5-May-2022 diff --git a/docs/cmd/hrp_har2case.md b/docs/cmd/hrp_har2case.md index 3fd38c9d..852c2bc2 100644 --- a/docs/cmd/hrp_har2case.md +++ b/docs/cmd/hrp_har2case.md @@ -24,4 +24,4 @@ hrp har2case $har_path... [flags] * [hrp](hrp.md) - Next-Generation API Testing Solution. -###### Auto generated by spf13/cobra on 4-May-2022 +###### Auto generated by spf13/cobra on 5-May-2022 diff --git a/docs/cmd/hrp_pytest.md b/docs/cmd/hrp_pytest.md index 29678585..6bc851e2 100644 --- a/docs/cmd/hrp_pytest.md +++ b/docs/cmd/hrp_pytest.md @@ -16,4 +16,4 @@ hrp pytest $path ... [flags] * [hrp](hrp.md) - Next-Generation API Testing Solution. -###### Auto generated by spf13/cobra on 4-May-2022 +###### Auto generated by spf13/cobra on 5-May-2022 diff --git a/docs/cmd/hrp_run.md b/docs/cmd/hrp_run.md index 86c7018d..d0125914 100644 --- a/docs/cmd/hrp_run.md +++ b/docs/cmd/hrp_run.md @@ -35,4 +35,4 @@ hrp run $path... [flags] * [hrp](hrp.md) - Next-Generation API Testing Solution. -###### Auto generated by spf13/cobra on 4-May-2022 +###### Auto generated by spf13/cobra on 5-May-2022 diff --git a/docs/cmd/hrp_startproject.md b/docs/cmd/hrp_startproject.md index 95fa28e6..d459b838 100644 --- a/docs/cmd/hrp_startproject.md +++ b/docs/cmd/hrp_startproject.md @@ -20,4 +20,4 @@ hrp startproject $project_name [flags] * [hrp](hrp.md) - Next-Generation API Testing Solution. -###### Auto generated by spf13/cobra on 4-May-2022 +###### Auto generated by spf13/cobra on 5-May-2022 diff --git a/examples/demo-with-go-plugin/plugin/go.mod b/examples/demo-with-go-plugin/plugin/go.mod index 941628d8..8dabb414 100644 --- a/examples/demo-with-go-plugin/plugin/go.mod +++ b/examples/demo-with-go-plugin/plugin/go.mod @@ -2,4 +2,4 @@ module plugin go 1.16 -require github.com/httprunner/funplugin v0.4.3 // indirect +require github.com/httprunner/funplugin v0.4.5 // indirect diff --git a/examples/demo-with-go-plugin/plugin/go.sum b/examples/demo-with-go-plugin/plugin/go.sum index 93a4421a..33c01bfb 100644 --- a/examples/demo-with-go-plugin/plugin/go.sum +++ b/examples/demo-with-go-plugin/plugin/go.sum @@ -58,8 +58,8 @@ github.com/hashicorp/go-plugin v1.4.3 h1:DXmvivbWD5qdiBts9TpBC7BYL1Aia5sxbRgQB+v github.com/hashicorp/go-plugin v1.4.3/go.mod h1:5fGEH17QVwTTcR0zV7yhDPLLmFX9YSZ38b18Udy6vYQ= github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb h1:b5rjCoWHc7eqmAS4/qyk21ZsHyb6Mxv/jykxvNTkU4M= github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= -github.com/httprunner/funplugin v0.4.3 h1:mxdxQh54NZLQnK/FXZxpZV0rhqZQzckrWKEnBW5w2Vg= -github.com/httprunner/funplugin v0.4.3/go.mod h1:vPyeJIfbpGe0epZZtAV0wCn16gLY9+imSw/zfxq0Lcc= +github.com/httprunner/funplugin v0.4.5 h1:2KCj5AZZA22OER6TN5P/PSBYFMiKpgTmCRbDmHB1tos= +github.com/httprunner/funplugin v0.4.5/go.mod h1:vPyeJIfbpGe0epZZtAV0wCn16gLY9+imSw/zfxq0Lcc= github.com/jhump/protoreflect v1.6.0/go.mod h1:eaTn3RZAmMBcV0fifFvlm6VHNz3wSkYyXYWUh7ymB74= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= diff --git a/go.mod b/go.mod index cdba9350..dbe79c4c 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/getsentry/sentry-go v0.13.0 github.com/google/uuid v1.3.0 github.com/gorilla/websocket v1.4.1 - github.com/httprunner/funplugin v0.4.4 + github.com/httprunner/funplugin v0.4.5 github.com/jinzhu/copier v0.3.2 github.com/jmespath/go-jmespath v0.4.0 github.com/json-iterator/go v1.1.12 diff --git a/go.sum b/go.sum index e72fb518..62502254 100644 --- a/go.sum +++ b/go.sum @@ -242,8 +242,8 @@ github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2p github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb h1:b5rjCoWHc7eqmAS4/qyk21ZsHyb6Mxv/jykxvNTkU4M= github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= -github.com/httprunner/funplugin v0.4.4 h1:IVt603Y57WfSbn6DZ0R4iLeGQJ1yj944gmYwEOSBzGo= -github.com/httprunner/funplugin v0.4.4/go.mod h1:vPyeJIfbpGe0epZZtAV0wCn16gLY9+imSw/zfxq0Lcc= +github.com/httprunner/funplugin v0.4.5 h1:2KCj5AZZA22OER6TN5P/PSBYFMiKpgTmCRbDmHB1tos= +github.com/httprunner/funplugin v0.4.5/go.mod h1:vPyeJIfbpGe0epZZtAV0wCn16gLY9+imSw/zfxq0Lcc= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/imkira/go-interpol v1.1.0/go.mod h1:z0h2/2T3XF8kyEPpRgJ3kmNv+C43p+I/CoI+jC3w2iA= From 47557a403bab95f32c3107b01f18d786256d9229 Mon Sep 17 00:00:00 2001 From: debugtalk <mail@debugtalk.com> Date: Thu, 5 May 2022 16:03:30 +0800 Subject: [PATCH 018/109] fix: unittest --- hrp/internal/httpstat/main.go | 58 +++++++++++++++++------------------ hrp/step_request.go | 15 ++++----- hrp/step_request_test.go | 6 ++-- 3 files changed, 39 insertions(+), 40 deletions(-) diff --git a/hrp/internal/httpstat/main.go b/hrp/internal/httpstat/main.go index 8d8ccc0d..e5731898 100644 --- a/hrp/internal/httpstat/main.go +++ b/hrp/internal/httpstat/main.go @@ -7,6 +7,7 @@ import ( "context" "crypto/tls" "fmt" + "net/http" "net/http/httptrace" "strconv" "strings" @@ -93,6 +94,9 @@ type Stat struct { // isReused is true when connection is reused (keep-alive) isReused bool + + // https or http + schema string } // Finish sets the time when reading response is done. @@ -110,11 +114,6 @@ func (s *Stat) Finish() { s.Total = s.transferDone.Sub(s.dnsStart) } -func (s *Stat) assertSchemaHTTP() bool { - // HTTP other than HTTPS - return s.dnsStart.IsZero() && s.tcpStart.IsZero() -} - // Durations returns all durations and timelines of request latencies func (s *Stat) Durations() map[string]int64 { return map[string]int64{ @@ -132,20 +131,8 @@ func (s *Stat) Durations() map[string]int64 { } func (s *Stat) Print() { - if s.assertSchemaHTTP() { - // http - printf(colorize(httpTemplate), - fmta(s.DNSLookup), // dns lookup - fmta(s.TCPConnection), // tcp connection - fmta(s.ServerProcessing), // server processing - fmta(s.ContentTransfer), // content transfer - fmtb(s.NameLookup), // namelookup - fmtb(s.Connect), // connect - fmtb(s.StartTransfer), // starttransfer - fmtb(s.Total), // total - ) - } else { - // https + switch s.schema { + case "https": printf(colorize(httpsTemplate), fmta(s.DNSLookup), // dns lookup fmta(s.TCPConnection), // tcp connection @@ -158,6 +145,17 @@ func (s *Stat) Print() { fmtb(s.StartTransfer), // starttransfer fmtb(s.Total), // total ) + case "http": + printf(colorize(httpTemplate), + fmta(s.DNSLookup), // dns lookup + fmta(s.TCPConnection), // tcp connection + fmta(s.ServerProcessing), // server processing + fmta(s.ContentTransfer), // content transfer + fmtb(s.NameLookup), // namelookup + fmtb(s.Connect), // connect + fmtb(s.StartTransfer), // starttransfer + fmtb(s.Total), // total + ) } log.Info(). Interface("httpstat(ms)", s.Durations()). @@ -166,8 +164,9 @@ func (s *Stat) Print() { // WithHTTPStat is a wrapper of httptrace.WithClientTrace. // It records the time of each httptrace hooks. -func WithHTTPStat(ctx context.Context, s *Stat) context.Context { - return httptrace.WithClientTrace(ctx, &httptrace.ClientTrace{ +func WithHTTPStat(req *http.Request, s *Stat) context.Context { + s.schema = req.URL.Scheme + return httptrace.WithClientTrace(req.Context(), &httptrace.ClientTrace{ DNSStart: func(i httptrace.DNSStartInfo) { s.dnsStart = time.Now() }, @@ -176,7 +175,7 @@ func WithHTTPStat(ctx context.Context, s *Stat) context.Context { s.dnsDone = time.Now() s.DNSLookup = s.dnsDone.Sub(s.dnsStart) - s.NameLookup = s.dnsDone.Sub(s.dnsStart) + s.NameLookup = s.DNSLookup }, ConnectStart: func(_, _ string) { @@ -215,12 +214,11 @@ func WithHTTPStat(ctx context.Context, s *Stat) context.Context { }, WroteRequest: func(info httptrace.WroteRequestInfo) { - s.serverStart = time.Now() + now := time.Now() + s.serverStart = now - // When client doesn't use DialContext or using old (before go1.7) `net` - // package, DNS/TCP/TLS hook is not called. - if s.assertSchemaHTTP() { - now := s.serverStart + // When client doesn't use DialContext, DNS/TCP/TLS hook is not called. + if s.dnsStart.IsZero() && s.tcpStart.IsZero() { s.dnsStart = now s.dnsDone = now s.tcpStart = now @@ -229,7 +227,6 @@ func WithHTTPStat(ctx context.Context, s *Stat) context.Context { // When connection is re-used, DNS/TCP/TLS hook is not called. if s.isReused { - now := s.serverStart s.dnsStart = now s.dnsDone = now s.tcpStart = now @@ -238,11 +235,12 @@ func WithHTTPStat(ctx context.Context, s *Stat) context.Context { s.tlsDone = now } - if s.isTLS { + if s.isTLS { // https return } - s.TLSHandshake = s.tcpDone.Sub(s.tcpDone) + // http + s.TLSHandshake = 0 s.Pretransfer = s.Connect }, diff --git a/hrp/step_request.go b/hrp/step_request.go index 9dbae164..41e5d022 100644 --- a/hrp/step_request.go +++ b/hrp/step_request.go @@ -315,7 +315,7 @@ func runStepRequest(r *SessionRunner, step *TStep) (stepResult *StepResult, err // stat HTTP request var httpStat httpstat.Stat if r.HTTPStatOn() { - ctx := httpstat.WithHTTPStat(rb.req.Context(), &httpStat) + ctx := httpstat.WithHTTPStat(rb.req, &httpStat) rb.req = rb.req.WithContext(ctx) } @@ -347,12 +347,6 @@ func runStepRequest(r *SessionRunner, step *TStep) (stepResult *StepResult, err } } - if r.HTTPStatOn() { - httpStat.Finish() - stepResult.HttpStat = httpStat.Durations() - httpStat.Print() - } - // new response object respObj, err := newHttpResponseObject(r.hrpRunner.t, parser, resp) if err != nil { @@ -360,6 +354,13 @@ func runStepRequest(r *SessionRunner, step *TStep) (stepResult *StepResult, err return } + if r.HTTPStatOn() { + // resp.Body has been ReadAll + httpStat.Finish() + stepResult.HttpStat = httpStat.Durations() + httpStat.Print() + } + // add response object to step variables, could be used in teardown hooks stepVariables["hrp_step_response"] = respObj.respObjMeta diff --git a/hrp/step_request_test.go b/hrp/step_request_test.go index 254cdee1..46c62cae 100644 --- a/hrp/step_request_test.go +++ b/hrp/step_request_test.go @@ -120,7 +120,7 @@ func TestRunRequestStatOn(t *testing.T) { if !assert.GreaterOrEqual(t, stat["ContentTransfer"], int64(0)) { t.Fatal() } - if !assert.Greater(t, stat["NameLookup"], int64(0)) { + if !assert.GreaterOrEqual(t, stat["NameLookup"], int64(0)) { t.Fatal() } if !assert.Greater(t, stat["Connect"], int64(0)) { @@ -132,7 +132,7 @@ func TestRunRequestStatOn(t *testing.T) { if !assert.Greater(t, stat["StartTransfer"], int64(0)) { t.Fatal() } - if !assert.Greater(t, stat["Total"], int64(10)) { + if !assert.Greater(t, stat["Total"], int64(5)) { t.Fatal() } if !assert.Less(t, stat["Total"]-summary.Records[0].Elapsed, int64(2)) { @@ -168,7 +168,7 @@ func TestRunRequestStatOn(t *testing.T) { if !assert.Greater(t, stat["StartTransfer"], int64(0)) { t.Fatal() } - if !assert.Greater(t, stat["Total"], int64(10)) { + if !assert.Greater(t, stat["Total"], int64(1)) { t.Fatal() } if !assert.Less(t, stat["Total"]-summary.Records[0].Elapsed, int64(2)) { From f2dbd241245a19882da7419b0bb539c3a73f4711 Mon Sep 17 00:00:00 2001 From: debugtalk <mail@debugtalk.com> Date: Thu, 5 May 2022 21:20:20 +0800 Subject: [PATCH 019/109] feat: print connected network info --- hrp/internal/httpstat/main.go | 16 +++++++++++++++- hrp/step_request.go | 16 ++++++++++++++++ hrp/step_request_test.go | 19 ++++++++----------- 3 files changed, 39 insertions(+), 12 deletions(-) diff --git a/hrp/internal/httpstat/main.go b/hrp/internal/httpstat/main.go index e5731898..29bf464d 100644 --- a/hrp/internal/httpstat/main.go +++ b/hrp/internal/httpstat/main.go @@ -97,6 +97,9 @@ type Stat struct { // https or http schema string + + // connected network info + network, addr string } // Finish sets the time when reading response is done. @@ -131,6 +134,14 @@ func (s *Stat) Durations() map[string]int64 { } func (s *Stat) Print() { + if s.network != "" && s.addr != "" { + printf("\n%s %s: %s\n", + color.CyanString("Connected to"), + color.YellowString(s.network), + color.BlueString(s.addr), + ) + } + switch s.schema { case "https": printf(colorize(httpsTemplate), @@ -178,7 +189,10 @@ func WithHTTPStat(req *http.Request, s *Stat) context.Context { s.NameLookup = s.DNSLookup }, - ConnectStart: func(_, _ string) { + ConnectStart: func(network, addr string) { + s.network = network + s.addr = addr + s.tcpStart = time.Now() // When connecting to IP (When no DNS lookup) diff --git a/hrp/step_request.go b/hrp/step_request.go index 41e5d022..30c5988e 100644 --- a/hrp/step_request.go +++ b/hrp/step_request.go @@ -4,6 +4,7 @@ import ( "bytes" "compress/gzip" "compress/zlib" + "crypto/tls" "fmt" "io" "net/http" @@ -15,6 +16,7 @@ import ( "time" "github.com/andybalholm/brotli" + "github.com/fatih/color" "github.com/pkg/errors" "github.com/rs/zerolog/log" @@ -423,8 +425,22 @@ func printRequest(req *http.Request) error { return nil } +func printf(format string, a ...interface{}) (n int, err error) { + return fmt.Fprintf(color.Output, format, a...) +} + func printResponse(resp *http.Response) error { fmt.Println("==================== response ====================") + connectedVia := "plaintext" + if resp.TLS != nil { + switch resp.TLS.Version { + case tls.VersionTLS12: + connectedVia = "TLSv1.2" + case tls.VersionTLS13: + connectedVia = "TLSv1.3" + } + } + printf("%s %s\n", color.CyanString("Connected via"), color.BlueString("%s", connectedVia)) respContentType := resp.Header.Get("Content-Type") printBody := shouldPrintBody(respContentType) respDump, err := httputil.DumpResponse(resp, printBody) diff --git a/hrp/step_request_test.go b/hrp/step_request_test.go index 46c62cae..1076b31d 100644 --- a/hrp/step_request_test.go +++ b/hrp/step_request_test.go @@ -135,34 +135,31 @@ func TestRunRequestStatOn(t *testing.T) { if !assert.Greater(t, stat["Total"], int64(5)) { t.Fatal() } - if !assert.Less(t, stat["Total"]-summary.Records[0].Elapsed, int64(2)) { + if !assert.Less(t, stat["Total"]-summary.Records[0].Elapsed, int64(3)) { t.Fatal() } // reuse connection stat = summary.Records[1].HttpStat - if !assert.Equal(t, stat["DNSLookup"], int64(0)) { + if !assert.Equal(t, int64(0), stat["DNSLookup"]) { t.Fatal() } - if !assert.Equal(t, stat["TCPConnection"], int64(0)) { + if !assert.Equal(t, int64(0), stat["TCPConnection"]) { t.Fatal() } - if !assert.Equal(t, stat["TLSHandshake"], int64(0)) { + if !assert.Equal(t, int64(0), stat["TLSHandshake"]) { t.Fatal() } if !assert.Greater(t, stat["ServerProcessing"], int64(1)) { t.Fatal() } - if !assert.Equal(t, stat["ContentTransfer"], int64(0)) { + if !assert.Equal(t, int64(0), stat["NameLookup"]) { t.Fatal() } - if !assert.Equal(t, stat["NameLookup"], int64(0)) { + if !assert.Equal(t, int64(0), stat["Connect"]) { t.Fatal() } - if !assert.Equal(t, stat["Connect"], int64(0)) { - t.Fatal() - } - if !assert.Equal(t, stat["Pretransfer"], int64(0)) { + if !assert.Equal(t, int64(0), stat["Pretransfer"]) { t.Fatal() } if !assert.Greater(t, stat["StartTransfer"], int64(0)) { @@ -171,7 +168,7 @@ func TestRunRequestStatOn(t *testing.T) { if !assert.Greater(t, stat["Total"], int64(1)) { t.Fatal() } - if !assert.Less(t, stat["Total"]-summary.Records[0].Elapsed, int64(2)) { + if !assert.Less(t, stat["Total"]-summary.Records[0].Elapsed, int64(3)) { t.Fatal() } } From 654edabe782662ae59c605ac3beff60b2c467f18 Mon Sep 17 00:00:00 2001 From: "duanchao.bill" <duanchao.bill@bytedance.com> Date: Fri, 6 May 2022 22:37:04 +0800 Subject: [PATCH 020/109] fix: codeview --- httprunner/__init__.py | 28 +++++------- httprunner/models.py | 20 ++++----- httprunner/runner.py | 4 +- httprunner/step.py | 71 +++++------------------------- httprunner/step_sql_request.py | 3 +- httprunner/step_thrift_request.py | 19 +++++--- httprunner/thrift/thrift_client.py | 43 +++++++++--------- 7 files changed, 72 insertions(+), 116 deletions(-) diff --git a/httprunner/__init__.py b/httprunner/__init__.py index e70b5526..d397cfae 100644 --- a/httprunner/__init__.py +++ b/httprunner/__init__.py @@ -1,17 +1,22 @@ __version__ = "4.0.0-beta" __description__ = "One-stop solution for HTTP(S) testing." + from httprunner.config import Config -import platform from httprunner.parser import parse_parameters as Parameters from httprunner.runner import HttpRunner from httprunner.step import Step from httprunner.step_request import RunRequest -from httprunner.step_testcase import RunTestCase from httprunner.step_sql_request import ( RunSqlRequest, - StepSqlRequestValidation, StepSqlRequestExtraction, + StepSqlRequestValidation, +) +from httprunner.step_testcase import RunTestCase +from httprunner.step_thrift_request import ( + RunThriftRequest, + StepThriftRequestExtraction, + StepThriftRequestValidation, ) __all__ = [ @@ -26,18 +31,7 @@ __all__ = [ "StepSqlRequestExtraction", "RunTestCase", "Parameters", + "RunThriftRequest", + "StepThriftRequestValidation", + "StepThriftRequestExtraction", ] -if platform.system() != "Windows": - from httprunner.step_thrift_request import ( - RunThriftRequest, - StepThriftRequestValidation, - StepThriftRequestExtraction, - ) - - __all__.extend( - [ - "RunThriftRequest", - "StepThriftRequestValidation", - "StepThriftRequestExtraction", - ] - ) diff --git a/httprunner/models.py b/httprunner/models.py index 487371fe..7994240c 100644 --- a/httprunner/models.py +++ b/httprunner/models.py @@ -29,17 +29,17 @@ class MethodEnum(Text, Enum): class ProtoType(Enum): - pBinary = 1 - pCyBinary = 2 - pCompact = 3 - pJson = 4 + Binary = 1 + CyBinary = 2 + Compact = 3 + Json = 4 class TransType(Enum): - tBuffered = 1 - tCyBuffered = 2 - tFramed = 3 - tCyFramed = 4 + Buffered = 1 + CyBuffered = 2 + Framed = 3 + CyFramed = 4 # configs for thrift rpc @@ -56,8 +56,8 @@ class TConfigThrift(BaseModel): ip: Text = "127.0.0.1" port: int = 9000 service_name: Text = None - proto_type: ProtoType = ProtoType.pBinary - trans_type: TransType = TransType.tBuffered + proto_type: ProtoType = ProtoType.Binary + trans_type: TransType = TransType.Buffered # configs for db diff --git a/httprunner/runner.py b/httprunner/runner.py index 558a4fa9..28b1022d 100644 --- a/httprunner/runner.py +++ b/httprunner/runner.py @@ -91,9 +91,11 @@ class SessionRunner(object): def with_thrift_client(self, thrift_client) -> "SessionRunner": self.thrift_client = thrift_client + return self - def with_db_engine(self, db_engine): + def with_db_engine(self, db_engine) -> "SessionRunner": self.db_engine = db_engine + return self def __parse_config(self, param: Dict = None) -> None: # parse config variables diff --git a/httprunner/step.py b/httprunner/step.py index b0d20a9b..4e3edc12 100644 --- a/httprunner/step.py +++ b/httprunner/step.py @@ -1,18 +1,20 @@ -import platform from typing import Union -from httprunner.models import StepResult, TRequest, TStep, TestCase -from httprunner.runner import HttpRunner from httprunner.step_request import ( RequestWithOptionalArgs, StepRequestExtraction, StepRequestValidation, ) -from httprunner.step_testcase import StepRefCase from httprunner.step_sql_request import ( RunSqlRequest, - StepSqlRequestValidation, StepSqlRequestExtraction, + StepSqlRequestValidation, +) +from httprunner.step_testcase import StepRefCase +from httprunner.step_thrift_request import ( + RunThriftRequest, + StepThriftRequestExtraction, + StepThriftRequestValidation, ) @@ -27,60 +29,9 @@ class Step(object): RunSqlRequest, StepSqlRequestValidation, StepSqlRequestExtraction, + RunThriftRequest, + StepThriftRequestValidation, + StepThriftRequestExtraction, ], ): - self.__step = step - - @property - def request(self) -> TRequest: - return self.__step.struct().request - - @property - def testcase(self) -> TestCase: - return self.__step.struct().testcase - - @property - def retry_times(self) -> int: - return self.__step.struct().retry_times - - @property - def retry_interval(self) -> int: - return self.__step.struct().retry_interval - - def struct(self) -> TStep: - return self.__step.struct() - - def name(self) -> str: - return self.__step.name() - - def type(self) -> str: - return self.__step.type() - - def run(self, runner: HttpRunner) -> StepResult: - return self.__step.run(runner) - - -if platform.system() != "Windows": - from httprunner.step_thrift_request import ( - RunThriftRequest, - StepThriftRequestValidation, - StepThriftRequestExtraction, - ) - - class Step(Step): - def __init__( - self, - step: Union[ - StepRequestValidation, - StepRequestExtraction, - RequestWithOptionalArgs, - StepRefCase, - RunSqlRequest, - StepSqlRequestValidation, - StepSqlRequestExtraction, - RunThriftRequest, - StepThriftRequestValidation, - StepThriftRequestExtraction, - ], - ): - super().__init__(step) + super().__init__(step) diff --git a/httprunner/step_sql_request.py b/httprunner/step_sql_request.py index 7851a3be..c7032fae 100644 --- a/httprunner/step_sql_request.py +++ b/httprunner/step_sql_request.py @@ -15,7 +15,6 @@ from httprunner.step_request import ( StepRequestExtraction, StepRequestValidation, ) -from httprunner.database.engine import DBEngine from httprunner.exceptions import SqlMethodNotSupport @@ -52,6 +51,8 @@ def run_step_sql_request(runner: HttpRunner, step: TStep) -> StepResult: ) if not runner.db_engine: + from httprunner.database.engine import DBEngine + runner.db_engine = DBEngine( f'mysql+pymysql://{parsed_request_dict["db_config"]["user"]}:' f'{parsed_request_dict["db_config"]["password"]}@{parsed_request_dict["db_config"]["ip"]}:' diff --git a/httprunner/step_thrift_request.py b/httprunner/step_thrift_request.py index 919df130..63acdd5d 100644 --- a/httprunner/step_thrift_request.py +++ b/httprunner/step_thrift_request.py @@ -1,21 +1,26 @@ # -*- coding: utf-8 -*- import time from typing import Text, Union + from loguru import logger from httprunner import utils from httprunner.exceptions import ValidationFailure -from httprunner.models import IStep, StepResult, TStep, ProtoType, TransType +from httprunner.models import ( + IStep, + ProtoType, + StepResult, + TStep, + TThriftRequest, + TransType, +) +from httprunner.response import ThriftResponseObject from httprunner.runner import HttpRunner from httprunner.step_request import ( - call_hooks, StepRequestExtraction, StepRequestValidation, + call_hooks, ) -from httprunner.models import TThriftRequest -from httprunner.response import ThriftResponseObject - -from httprunner.thrift.thrift_client import ThriftClient def run_step_thrift_request(runner: HttpRunner, step: TStep) -> StepResult: @@ -71,6 +76,8 @@ def run_step_thrift_request(runner: HttpRunner, step: TStep) -> StepResult: if not runner.thrift_client: runner.thrift_client = parsed_request_dict["thrift_client"] if not runner.thrift_client: + from httprunner.thrift.thrift_client import ThriftClient + runner.thrift_client = ThriftClient( thrift_file=parsed_request_dict["idl_path"], service_name=parsed_request_dict["service_name"], diff --git a/httprunner/thrift/thrift_client.py b/httprunner/thrift/thrift_client.py index 59cd2d5f..13ad6f30 100644 --- a/httprunner/thrift/thrift_client.py +++ b/httprunner/thrift/thrift_client.py @@ -1,10 +1,11 @@ # -*- coding: utf-8 -*- from __future__ import absolute_import +import platform import enum import json -from loguru import logger import thriftpy2 +from loguru import logger from thriftpy2.protocol import ( TBinaryProtocolFactory, TCompactProtocolFactory, @@ -18,23 +19,22 @@ from thriftpy2.transport import ( TCyFramedTransportFactory, TFramedTransportFactory, ) -from thriftpy2.utils import deserialize -from httprunner.thrift.data_convertor import json2thrift, thrift2json, thrift2dict +from httprunner.thrift.data_convertor import json2thrift, thrift2dict class ProtoType(enum.Enum): - pBinary = 1 - pCyBinary = 2 - pCompact = 3 - pJson = 4 + Binary = 1 + CyBinary = 2 + Compact = 3 + Json = 4 class TransType(enum.Enum): - tBuffered = 1 - tCyBuffered = 2 - tFramed = 3 - tCyFramed = 4 + Buffered = 1 + CyBuffered = 2 + Framed = 3 + CyFramed = 4 class RequestFormat(enum.Enum): @@ -43,24 +43,24 @@ class RequestFormat(enum.Enum): def get_proto_factory(proto_type): - if proto_type == ProtoType.pBinary: + if proto_type == ProtoType.Binary: return TBinaryProtocolFactory() - if proto_type == ProtoType.pCyBinary: + if proto_type == ProtoType.CyBinary: return TCyBinaryProtocolFactory() - if proto_type == ProtoType.pCompact: + if proto_type == ProtoType.Compact: return TCompactProtocolFactory() - if proto_type == ProtoType.pJson: + if proto_type == ProtoType.Json: return TJSONProtocolFactory() def get_trans_factory(trans_type): - if trans_type == TransType.tBuffered: + if trans_type == TransType.Buffered: return TBufferedTransportFactory() - if trans_type == TransType.tCyBuffered: + if trans_type == TransType.CyBuffered: return TCyBufferedTransportFactory() - if trans_type == TransType.tFramed: + if trans_type == TransType.Framed: return TFramedTransportFactory() - if trans_type == TransType.tCyFramed: + if trans_type == TransType.CyFramed: return TCyFramedTransportFactory() @@ -73,8 +73,8 @@ class ThriftClient(object): port, include_dirs=None, timeout=3000, - proto_type=ProtoType.pCyBinary, - trans_type=TransType.tCyBuffered, + proto_type=ProtoType.CyBinary, + trans_type=TransType.CyBuffered, ): self.thrift_file = thrift_file self.include_dirs = include_dirs @@ -84,6 +84,7 @@ class ThriftClient(object): self.timeout = timeout self.proto_type = proto_type self.trans_type = trans_type + assert platform.system() != "Windows", "thrift not support Windows for now" try: logger.debug( "init thrift module: thrift_file=%s, module_name=%s", From c69acad9e400a74368f9f79edacb9cbc053c4b21 Mon Sep 17 00:00:00 2001 From: "duanchao.bill" <duanchao.bill@bytedance.com> Date: Sat, 7 May 2022 10:42:55 +0800 Subject: [PATCH 021/109] =?UTF-8?q?fix:=20=E4=BE=9D=E8=B5=96=E6=94=B9?= =?UTF-8?q?=E4=B8=BA=E5=8F=AF=E9=80=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- poetry.lock | 25 ++++++++++++++++++++++++- pyproject.toml | 11 +++++++---- 2 files changed, 31 insertions(+), 5 deletions(-) diff --git a/poetry.lock b/poetry.lock index 2b202e54..7482ae2c 100644 --- a/poetry.lock +++ b/poetry.lock @@ -459,6 +459,23 @@ type = "legacy" url = "https://pypi.tuna.tsinghua.edu.cn/simple" reference = "tsinghua" +[[package]] +name = "pymysql" +version = "1.0.2" +description = "Pure Python MySQL Driver" +category = "main" +optional = true +python-versions = ">=3.6" + +[package.extras] +ed25519 = ["PyNaCl (>=1.4.0)"] +rsa = ["cryptography"] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "tsinghua" + [[package]] name = "pyparsing" version = "3.0.7" @@ -815,12 +832,14 @@ reference = "tsinghua" [extras] allure = ["allure-pytest"] +sql = ["sqlalchemy", "pymysql"] +thrift = ["cython", "thrift", "thriftpy2"] upload = ["requests-toolbelt", "filetype"] [metadata] lock-version = "1.1" python-versions = "^3.7" -content-hash = "15f15916b41e180ee7567716713607183f5d8c8d30300c24dada98be454bc675" +content-hash = "a00de4a66e9c8b73709f339d266be673ca6057dfd4023504677054697611986d" [metadata.files] allure-pytest = [ @@ -1195,6 +1214,10 @@ pydantic = [ {file = "pydantic-1.8.2-py3-none-any.whl", hash = "sha256:fec866a0b59f372b7e776f2d7308511784dace622e0992a0b59ea3ccee0ae833"}, {file = "pydantic-1.8.2.tar.gz", hash = "sha256:26464e57ccaafe72b7ad156fdaa4e9b9ef051f69e175dbbb463283000c05ab7b"}, ] +pymysql = [ + {file = "PyMySQL-1.0.2-py3-none-any.whl", hash = "sha256:41fc3a0c5013d5f039639442321185532e3e2c8924687abe6537de157d403641"}, + {file = "PyMySQL-1.0.2.tar.gz", hash = "sha256:816927a350f38d56072aeca5dfb10221fe1dc653745853d30a216637f5d7ad36"}, +] pyparsing = [ {file = "pyparsing-3.0.7-py3-none-any.whl", hash = "sha256:a6c06a88f252e6c322f65faf8f418b16213b51bdfaece0524c1c1bc30c63c484"}, {file = "pyparsing-3.0.7.tar.gz", hash = "sha256:18ee9022775d270c55187733956460083db60b37d0d0fb357445f3094eed3eea"}, diff --git a/pyproject.toml b/pyproject.toml index eaa1d97b..02975083 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -45,14 +45,17 @@ filetype = {version = "^1.0.7", optional = true} Brotli = "^1.0.9" jinja2 = "^3.0.3" toml = "^0.10.2" -sqlalchemy = "^1.4.36" -cython = "^0.29.28" -thriftpy2 = "^0.4.14" -thrift = "^0.16.0" +sqlalchemy = {version = "^1.4.36", optional = true} +pymysql = {version = "^1.0.2",optional = true} +cython = {version = "^0.29.28", optional = true} +thriftpy2 = {version = "^0.4.14", optional = true} +thrift = {version = "^0.16.0", optional = true} [tool.poetry.extras] allure = ["allure-pytest"] # pip install "httprunner[allure]", poetry install -E allure upload = ["requests-toolbelt", "filetype"] # pip install "httprunner[upload]", poetry install -E upload +sql = ["sqlalchemy","pymysql"] # pip install "httprunner[sql]", poetry install -E sql +thrift = ["cython","thrift","thriftpy2"] # pip install "httprunner[thrift]", poetry install -E sql [tool.poetry.dev-dependencies] coverage = "^4.5.4" From 86fad0ad702d72530416250352edf1883234accb Mon Sep 17 00:00:00 2001 From: "duanchao.bill" <duanchao.bill@bytedance.com> Date: Sat, 7 May 2022 11:07:17 +0800 Subject: [PATCH 022/109] =?UTF-8?q?fix:thrift/sql=E6=94=B9=E4=B8=BA?= =?UTF-8?q?=E5=8F=AF=E9=80=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- httprunner/step_sql_request.py | 38 ++++++++++++++++----- httprunner/step_thrift_request.py | 53 ++++++++++++++++++++++-------- httprunner/thrift/thrift_client.py | 3 +- 3 files changed, 70 insertions(+), 24 deletions(-) diff --git a/httprunner/step_sql_request.py b/httprunner/step_sql_request.py index c7032fae..cc5ddfba 100644 --- a/httprunner/step_sql_request.py +++ b/httprunner/step_sql_request.py @@ -1,21 +1,41 @@ # -*- coding: utf-8 -*- +import sys import time from typing import Text - from loguru import logger from httprunner import utils +from httprunner.exceptions import SqlMethodNotSupport from httprunner.exceptions import ValidationFailure from httprunner.models import IStep, StepResult, TStep -from httprunner.models import TSqlRequest, SqlMethodEnum +from httprunner.models import SqlMethodEnum, TSqlRequest from httprunner.response import SqlResponseObject from httprunner.runner import HttpRunner -from httprunner.step_request import ( - call_hooks, - StepRequestExtraction, - StepRequestValidation, -) -from httprunner.exceptions import SqlMethodNotSupport +from httprunner.step_request import (StepRequestExtraction, StepRequestValidation, call_hooks) + +try: + import sqlalchemy + import pymysql + + SQL_READY = True +except ModuleNotFoundError: + SQL_READY = False + + +def ensure_sql_ready(): + if SQL_READY: + return + + msg = """ + uploader extension dependencies uninstalled, install first and try again. + install with pip: + $ pip install sqlalchemy pymysql + + or you can install httprunner with optional upload dependencies: + $ pip install "httprunner[sql]" + """ + logger.error(msg) + sys.exit(1) def run_step_sql_request(runner: HttpRunner, step: TStep) -> StepResult: @@ -51,8 +71,8 @@ def run_step_sql_request(runner: HttpRunner, step: TStep) -> StepResult: ) if not runner.db_engine: + ensure_sql_ready() from httprunner.database.engine import DBEngine - runner.db_engine = DBEngine( f'mysql+pymysql://{parsed_request_dict["db_config"]["user"]}:' f'{parsed_request_dict["db_config"]["password"]}@{parsed_request_dict["db_config"]["ip"]}:' diff --git a/httprunner/step_thrift_request.py b/httprunner/step_thrift_request.py index 63acdd5d..bef05448 100644 --- a/httprunner/step_thrift_request.py +++ b/httprunner/step_thrift_request.py @@ -1,4 +1,6 @@ # -*- coding: utf-8 -*- +import platform +import sys import time from typing import Text, Union @@ -22,6 +24,31 @@ from httprunner.step_request import ( call_hooks, ) +try: + import thriftpy2 + from thrift.Thrift import TType + + THRIFT_READY = True +except ModuleNotFoundError: + THRIFT_READY = False + + +def ensure_thrift_ready(): + assert platform.system() != "Windows", "Sorry,thrift not support Windows for now" + if THRIFT_READY: + return + + msg = """ + uploader extension dependencies uninstalled, install first and try again. + install with pip: + $ pip install cython thriftpy2 thrift + + or you can install httprunner with optional upload dependencies: + $ pip install "httprunner[thrift]" + """ + logger.error(msg) + sys.exit(1) + def run_step_thrift_request(runner: HttpRunner, step: TStep) -> StepResult: """run teststep:thrift request""" @@ -39,30 +66,30 @@ def run_step_thrift_request(runner: HttpRunner, step: TStep) -> StepResult: parsed_request_dict["psm"] = parsed_request_dict["psm"] or config.thrift.psm parsed_request_dict["env"] = parsed_request_dict["env"] or config.thrift.env parsed_request_dict["cluster"] = ( - parsed_request_dict["cluster"] or config.thrift.cluster + parsed_request_dict["cluster"] or config.thrift.cluster ) parsed_request_dict["idl_path"] = ( - parsed_request_dict["idl_path"] or config.thrift.idl_path + parsed_request_dict["idl_path"] or config.thrift.idl_path ) parsed_request_dict["include_dirs"] = ( - parsed_request_dict["include_dirs"] or config.thrift.include_dirs + parsed_request_dict["include_dirs"] or config.thrift.include_dirs ) parsed_request_dict["method"] = ( - parsed_request_dict["method"] or config.thrift.method + parsed_request_dict["method"] or config.thrift.method ) parsed_request_dict["service_name"] = ( - parsed_request_dict["service_name"] or config.thrift.service_name + parsed_request_dict["service_name"] or config.thrift.service_name ) parsed_request_dict["ip"] = parsed_request_dict["ip"] or config.thrift.ip parsed_request_dict["port"] = parsed_request_dict["port"] or config.thrift.port parsed_request_dict["proto_type"] = ( - parsed_request_dict["proto_type"] or config.thrift.proto_type + parsed_request_dict["proto_type"] or config.thrift.proto_type ) parsed_request_dict["trans_port"] = ( - parsed_request_dict["trans_type"] or config.thrift.trans_type + parsed_request_dict["trans_type"] or config.thrift.trans_type ) parsed_request_dict["timeout"] = ( - parsed_request_dict["timeout"] or config.thrift.timeout + parsed_request_dict["timeout"] or config.thrift.timeout ) parsed_request_dict["thrift_client"] = parsed_request_dict["thrift_client"] @@ -76,8 +103,8 @@ def run_step_thrift_request(runner: HttpRunner, step: TStep) -> StepResult: if not runner.thrift_client: runner.thrift_client = parsed_request_dict["thrift_client"] if not runner.thrift_client: + ensure_thrift_ready() from httprunner.thrift.thrift_client import ThriftClient - runner.thrift_client = ThriftClient( thrift_file=parsed_request_dict["idl_path"], service_name=parsed_request_dict["service_name"], @@ -187,7 +214,7 @@ class RunThriftRequest(IStep): return self def teardown_hook( - self, hook: Text, assign_var_name: Text = None + self, hook: Text, assign_var_name: Text = None ) -> "RunThriftRequest": if assign_var_name: self.__step.teardown_hooks.append({assign_var_name: hook}) @@ -197,7 +224,7 @@ class RunThriftRequest(IStep): return self def setup_hook( - self, hook: Text, assign_var_name: Text = None + self, hook: Text, assign_var_name: Text = None ) -> "RunThriftRequest": if assign_var_name: self.__step.setup_hooks.append({assign_var_name: hook}) @@ -220,7 +247,7 @@ class RunThriftRequest(IStep): return self def with_thrift_client( - self, thrift_client: Union["ThriftClient", str] + self, thrift_client: Union["ThriftClient", str] ) -> "RunThriftRequest": self.__step.thrift_request.thrift_client = thrift_client return self @@ -260,7 +287,7 @@ class RunThriftRequest(IStep): return StepThriftRequestValidation(self.__step) def with_jmespath( - self, jmes_path: Text, var_name: Text + self, jmes_path: Text, var_name: Text ) -> "StepThriftRequestExtraction": self.__step.extract[var_name] = jmes_path return StepThriftRequestExtraction(self.__step) diff --git a/httprunner/thrift/thrift_client.py b/httprunner/thrift/thrift_client.py index 13ad6f30..8d5a3393 100644 --- a/httprunner/thrift/thrift_client.py +++ b/httprunner/thrift/thrift_client.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- from __future__ import absolute_import -import platform + import enum import json @@ -84,7 +84,6 @@ class ThriftClient(object): self.timeout = timeout self.proto_type = proto_type self.trans_type = trans_type - assert platform.system() != "Windows", "thrift not support Windows for now" try: logger.debug( "init thrift module: thrift_file=%s, module_name=%s", From 7e7571d4be73d2c3c1c56bc96e8b7df64e43451f Mon Sep 17 00:00:00 2001 From: "duanchao.bill" <duanchao.bill@bytedance.com> Date: Sat, 7 May 2022 11:11:36 +0800 Subject: [PATCH 023/109] fix: super().__init__(step) --- httprunner/step.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/httprunner/step.py b/httprunner/step.py index 4e3edc12..efcc12be 100644 --- a/httprunner/step.py +++ b/httprunner/step.py @@ -34,4 +34,4 @@ class Step(object): StepThriftRequestExtraction, ], ): - super().__init__(step) + self.__step = step From 4f79b81561a71bc67a47018e2638e8b1ec006ad9 Mon Sep 17 00:00:00 2001 From: "duanchao.bill" <duanchao.bill@bytedance.com> Date: Sat, 7 May 2022 11:30:08 +0800 Subject: [PATCH 024/109] =?UTF-8?q?fix:=20=E5=88=A0=E9=94=99=E4=BA=86?= =?UTF-8?q?=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- httprunner/step.py | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/httprunner/step.py b/httprunner/step.py index efcc12be..3b5f2535 100644 --- a/httprunner/step.py +++ b/httprunner/step.py @@ -1,5 +1,7 @@ from typing import Union +from httprunner import HttpRunner +from httprunner.models import StepResult, TRequest, TStep, TestCase from httprunner.step_request import ( RequestWithOptionalArgs, StepRequestExtraction, @@ -35,3 +37,31 @@ class Step(object): ], ): self.__step = step + + @property + def request(self) -> TRequest: + return self.__step.struct().request + + @property + def testcase(self) -> TestCase: + return self.__step.struct().testcase + + @property + def retry_times(self) -> int: + return self.__step.struct().retry_times + + @property + def retry_interval(self) -> int: + return self.__step.struct().retry_interval + + def struct(self) -> TStep: + return self.__step.struct() + + def name(self) -> str: + return self.__step.name() + + def type(self) -> str: + return self.__step.type() + + def run(self, runner: HttpRunner) -> StepResult: + return self.__step.run(runner) \ No newline at end of file From 2e0e48bffb72fa306c0e8dfd2e4fcdd35da0c74d Mon Sep 17 00:00:00 2001 From: debugtalk <mail@debugtalk.com> Date: Sat, 7 May 2022 18:51:31 +0800 Subject: [PATCH 025/109] fix: step request elapsed timming should contain ContentTransfer part --- docs/CHANGELOG.md | 6 ++++++ hrp/internal/version/VERSION | 2 +- hrp/step_request.go | 3 +-- httprunner/__init__.py | 2 +- pyproject.toml | 2 +- 5 files changed, 10 insertions(+), 5 deletions(-) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index adee1c8c..d5aeb4b0 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -1,5 +1,11 @@ # Release History +## v4.1.0-alpha (2022-05-07) + +**go version** + +- fix: step request elapsed timming should contain ContentTransfer part + ## v4.0.0 (2022-05-05) **go version** diff --git a/hrp/internal/version/VERSION b/hrp/internal/version/VERSION index f684230d..3a670224 100644 --- a/hrp/internal/version/VERSION +++ b/hrp/internal/version/VERSION @@ -1 +1 @@ -v4.0.0 \ No newline at end of file +v4.1.0-alpha \ No newline at end of file diff --git a/hrp/step_request.go b/hrp/step_request.go index 30c5988e..118be910 100644 --- a/hrp/step_request.go +++ b/hrp/step_request.go @@ -329,8 +329,6 @@ func runStepRequest(r *SessionRunner, step *TStep) (stepResult *StepResult, err } else { resp, err = r.hrpRunner.httpClient.Do(rb.req) } - - stepResult.Elapsed = time.Since(start).Milliseconds() if err != nil { return stepResult, errors.Wrap(err, "do request failed") } @@ -356,6 +354,7 @@ func runStepRequest(r *SessionRunner, step *TStep) (stepResult *StepResult, err return } + stepResult.Elapsed = time.Since(start).Milliseconds() if r.HTTPStatOn() { // resp.Body has been ReadAll httpStat.Finish() diff --git a/httprunner/__init__.py b/httprunner/__init__.py index 32ac1242..7adbd971 100644 --- a/httprunner/__init__.py +++ b/httprunner/__init__.py @@ -1,4 +1,4 @@ -__version__ = "v4.0.0" +__version__ = "v4.1.0-alpha" __description__ = "One-stop solution for HTTP(S) testing." diff --git a/pyproject.toml b/pyproject.toml index 9d6ea531..408206d0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "httprunner" -version = "v4.0.0" +version = "v4.1.0-alpha" description = "One-stop solution for HTTP(S) testing." license = "Apache-2.0" readme = "README.md" From a9f31d92bb76bec48655b0f603207b6440b5e792 Mon Sep 17 00:00:00 2001 From: debugtalk <mail@debugtalk.com> Date: Sat, 7 May 2022 18:52:18 +0800 Subject: [PATCH 026/109] bump version to v4.1.0-alpha --- docs/CHANGELOG.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index d5aeb4b0..443c1552 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -4,7 +4,12 @@ **go version** -- fix: step request elapsed timming should contain ContentTransfer part +- fix: step request elapsed timing should contain ContentTransfer part + +**python version** + +- feat: support new step type with SQL operation +- feat: support new step type with thrift protocol ## v4.0.0 (2022-05-05) From a6e427fd8ca1d9109431502a66c8fcc9cd47a2a7 Mon Sep 17 00:00:00 2001 From: debugtalk <mail@debugtalk.com> Date: Sat, 7 May 2022 18:55:55 +0800 Subject: [PATCH 027/109] change: trigger tests for branch v2/v3/v4.1-dev --- .github/workflows/hrp-scaffold.yml | 3 +++ .github/workflows/smoketest.yml | 3 +++ .github/workflows/unittest.yml | 3 +++ 3 files changed, 9 insertions(+) diff --git a/.github/workflows/hrp-scaffold.yml b/.github/workflows/hrp-scaffold.yml index 872dcd6a..726cc773 100644 --- a/.github/workflows/hrp-scaffold.yml +++ b/.github/workflows/hrp-scaffold.yml @@ -4,6 +4,9 @@ on: push: branches: - master + - v2 + - v3 + - v4.1-dev pull_request: env: diff --git a/.github/workflows/smoketest.yml b/.github/workflows/smoketest.yml index e08ea6e5..276deea4 100644 --- a/.github/workflows/smoketest.yml +++ b/.github/workflows/smoketest.yml @@ -4,6 +4,9 @@ on: push: branches: - master + - v2 + - v3 + - v4.1-dev pull_request: env: diff --git a/.github/workflows/unittest.yml b/.github/workflows/unittest.yml index c4235d0f..793edc30 100644 --- a/.github/workflows/unittest.yml +++ b/.github/workflows/unittest.yml @@ -4,6 +4,9 @@ on: push: branches: - master + - v2 + - v3 + - v4.1-dev pull_request: env: From 63af5ed55a1b734a1d1995eefdd5436f827b3ded Mon Sep 17 00:00:00 2001 From: debugtalk <mail@debugtalk.com> Date: Mon, 9 May 2022 15:14:52 +0800 Subject: [PATCH 028/109] change: update python dependencies --- poetry.lock | 139 +++++++++++++++++++++++----------------------------- 1 file changed, 62 insertions(+), 77 deletions(-) diff --git a/poetry.lock b/poetry.lock index 7482ae2c..bf7305fb 100644 --- a/poetry.lock +++ b/poetry.lock @@ -138,11 +138,11 @@ reference = "tsinghua" [[package]] name = "click" -version = "8.0.4" +version = "8.1.3" description = "Composable command line interface toolkit" category = "main" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" [package.dependencies] colorama = {version = "*", markers = "platform_system == \"Windows\""} @@ -184,7 +184,7 @@ name = "cython" version = "0.29.28" description = "The Cython compiler for writing C extensions for the Python language." category = "main" -optional = false +optional = true python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" [package.source] @@ -194,7 +194,7 @@ reference = "tsinghua" [[package]] name = "filetype" -version = "1.0.10" +version = "1.0.13" description = "Infer file type and MIME type of any file/buffer. No external dependencies." category = "main" optional = true @@ -210,7 +210,7 @@ name = "greenlet" version = "1.1.2" description = "Lightweight in-process concurrent programming" category = "main" -optional = false +optional = true python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*" [package.extras] @@ -271,7 +271,7 @@ reference = "tsinghua" [[package]] name = "jinja2" -version = "3.1.1" +version = "3.1.2" description = "A very fast and expressive template engine." category = "main" optional = false @@ -418,7 +418,7 @@ name = "ply" version = "3.11" description = "Python Lex & Yacc" category = "main" -optional = false +optional = true python-versions = "*" [package.source] @@ -478,14 +478,14 @@ reference = "tsinghua" [[package]] name = "pyparsing" -version = "3.0.7" -description = "Python parsing module" +version = "3.0.8" +description = "pyparsing module - Classes and methods to define and execute parsing grammars" category = "main" optional = false -python-versions = ">=3.6" +python-versions = ">=3.6.8" [package.extras] -diagrams = ["jinja2", "railroad-diagrams"] +diagrams = ["railroad-diagrams", "jinja2"] [package.source] type = "legacy" @@ -494,7 +494,7 @@ reference = "tsinghua" [[package]] name = "pytest" -version = "7.1.1" +version = "7.1.2" description = "pytest: simple powerful testing with Python" category = "main" optional = false @@ -640,7 +640,7 @@ name = "six" version = "1.16.0" description = "Python 2 and 3 compatibility utilities" category = "main" -optional = false +optional = true python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" [package.source] @@ -653,7 +653,7 @@ name = "sqlalchemy" version = "1.4.36" description = "Database Abstraction Library" category = "main" -optional = false +optional = true python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" [package.dependencies] @@ -691,7 +691,7 @@ name = "thrift" version = "0.16.0" description = "Python bindings for the Apache Thrift RPC system" category = "main" -optional = false +optional = true python-versions = "*" [package.dependencies] @@ -712,7 +712,7 @@ name = "thriftpy2" version = "0.4.14" description = "Pure python implementation of Apache Thrift." category = "main" -optional = false +optional = true python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [package.dependencies] @@ -755,7 +755,7 @@ reference = "tsinghua" [[package]] name = "typed-ast" -version = "1.5.2" +version = "1.5.3" description = "a fork of Python 2 and 3 ast modules with type comment support" category = "main" optional = false @@ -768,11 +768,11 @@ reference = "tsinghua" [[package]] name = "typing-extensions" -version = "4.1.1" -description = "Backported and Experimental Type Hints for Python 3.6+" +version = "4.2.0" +description = "Backported and Experimental Type Hints for Python 3.7+" category = "main" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" [package.source] type = "legacy" @@ -815,15 +815,15 @@ reference = "tsinghua" [[package]] name = "zipp" -version = "3.7.0" +version = "3.8.0" description = "Backport of pathlib-compatible object wrapper for zip files" category = "main" optional = false python-versions = ">=3.7" [package.extras] -docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] -testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy"] +docs = ["sphinx", "jaraco.packaging (>=9)", "rst.linker (>=1.9)"] +testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy (>=0.9.1)"] [package.source] type = "legacy" @@ -839,7 +839,7 @@ upload = ["requests-toolbelt", "filetype"] [metadata] lock-version = "1.1" python-versions = "^3.7" -content-hash = "a00de4a66e9c8b73709f339d266be673ca6057dfd4023504677054697611986d" +content-hash = "325219fc54f9d7133867db25ccabbb311459eeea519dc5a5a4a48290b80187d0" [metadata.files] allure-pytest = [ @@ -895,9 +895,6 @@ brotli = [ {file = "Brotli-1.0.9-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ee83d3e3a024a9618e5be64648d6d11c37047ac48adff25f12fa4226cf23d1c"}, {file = "Brotli-1.0.9-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:19598ecddd8a212aedb1ffa15763dd52a388518c4550e615aed88dc3753c0f0c"}, {file = "Brotli-1.0.9-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:44bb8ff420c1d19d91d79d8c3574b8954288bdff0273bf788954064d260d7ab0"}, - {file = "Brotli-1.0.9-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e23281b9a08ec338469268f98f194658abfb13658ee98e2b7f85ee9dd06caa91"}, - {file = "Brotli-1.0.9-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:3496fc835370da351d37cada4cf744039616a6db7d13c430035e901443a34daa"}, - {file = "Brotli-1.0.9-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b83bb06a0192cccf1eb8d0a28672a1b79c74c3a8a5f2619625aeb6f28b3a82bb"}, {file = "Brotli-1.0.9-cp310-cp310-win32.whl", hash = "sha256:26d168aac4aaec9a4394221240e8a5436b5634adc3cd1cdf637f6645cecbf181"}, {file = "Brotli-1.0.9-cp310-cp310-win_amd64.whl", hash = "sha256:622a231b08899c864eb87e85f81c75e7b9ce05b001e59bbfbf43d4a71f5f32b2"}, {file = "Brotli-1.0.9-cp35-cp35m-macosx_10_6_intel.whl", hash = "sha256:c83aa123d56f2e060644427a882a36b3c12db93727ad7a7b9efd7d7f3e9cc2c4"}, @@ -909,18 +906,12 @@ brotli = [ {file = "Brotli-1.0.9-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:40d15c79f42e0a2c72892bf407979febd9cf91f36f495ffb333d1d04cebb34e4"}, {file = "Brotli-1.0.9-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:93130612b837103e15ac3f9cbacb4613f9e348b58b3aad53721d92e57f96d46a"}, {file = "Brotli-1.0.9-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:87fdccbb6bb589095f413b1e05734ba492c962b4a45a13ff3408fa44ffe6479b"}, - {file = "Brotli-1.0.9-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:6d847b14f7ea89f6ad3c9e3901d1bc4835f6b390a9c71df999b0162d9bb1e20f"}, - {file = "Brotli-1.0.9-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:495ba7e49c2db22b046a53b469bbecea802efce200dffb69b93dd47397edc9b6"}, - {file = "Brotli-1.0.9-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:4688c1e42968ba52e57d8670ad2306fe92e0169c6f3af0089be75bbac0c64a3b"}, {file = "Brotli-1.0.9-cp36-cp36m-win32.whl", hash = "sha256:61a7ee1f13ab913897dac7da44a73c6d44d48a4adff42a5701e3239791c96e14"}, {file = "Brotli-1.0.9-cp36-cp36m-win_amd64.whl", hash = "sha256:1c48472a6ba3b113452355b9af0a60da5c2ae60477f8feda8346f8fd48e3e87c"}, {file = "Brotli-1.0.9-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:3b78a24b5fd13c03ee2b7b86290ed20efdc95da75a3557cc06811764d5ad1126"}, {file = "Brotli-1.0.9-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:9d12cf2851759b8de8ca5fde36a59c08210a97ffca0eb94c532ce7b17c6a3d1d"}, {file = "Brotli-1.0.9-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:6c772d6c0a79ac0f414a9f8947cc407e119b8598de7621f39cacadae3cf57d12"}, {file = "Brotli-1.0.9-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:29d1d350178e5225397e28ea1b7aca3648fcbab546d20e7475805437bfb0a130"}, - {file = "Brotli-1.0.9-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7bbff90b63328013e1e8cb50650ae0b9bac54ffb4be6104378490193cd60f85a"}, - {file = "Brotli-1.0.9-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:ec1947eabbaf8e0531e8e899fc1d9876c179fc518989461f5d24e2223395a9e3"}, - {file = "Brotli-1.0.9-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:12effe280b8ebfd389022aa65114e30407540ccb89b177d3fbc9a4f177c4bd5d"}, {file = "Brotli-1.0.9-cp37-cp37m-win32.whl", hash = "sha256:f909bbbc433048b499cb9db9e713b5d8d949e8c109a2a548502fb9aa8630f0b1"}, {file = "Brotli-1.0.9-cp37-cp37m-win_amd64.whl", hash = "sha256:97f715cf371b16ac88b8c19da00029804e20e25f30d80203417255d239f228b5"}, {file = "Brotli-1.0.9-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e16eb9541f3dd1a3e92b89005e37b1257b157b7256df0e36bd7b33b50be73bcb"}, @@ -928,9 +919,6 @@ brotli = [ {file = "Brotli-1.0.9-cp38-cp38-manylinux1_i686.whl", hash = "sha256:b663f1e02de5d0573610756398e44c130add0eb9a3fc912a09665332942a2efb"}, {file = "Brotli-1.0.9-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:5b6ef7d9f9c38292df3690fe3e302b5b530999fa90014853dcd0d6902fb59f26"}, {file = "Brotli-1.0.9-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8a674ac10e0a87b683f4fa2b6fa41090edfd686a6524bd8dedbd6138b309175c"}, - {file = "Brotli-1.0.9-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:e2d9e1cbc1b25e22000328702b014227737756f4b5bf5c485ac1d8091ada078b"}, - {file = "Brotli-1.0.9-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:b336c5e9cf03c7be40c47b5fd694c43c9f1358a80ba384a21969e0b4e66a9b17"}, - {file = "Brotli-1.0.9-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:85f7912459c67eaab2fb854ed2bc1cc25772b300545fe7ed2dc03954da638649"}, {file = "Brotli-1.0.9-cp38-cp38-win32.whl", hash = "sha256:35a3edbe18e876e596553c4007a087f8bcfd538f19bc116917b3c7522fca0429"}, {file = "Brotli-1.0.9-cp38-cp38-win_amd64.whl", hash = "sha256:269a5743a393c65db46a7bb982644c67ecba4b8d91b392403ad8a861ba6f495f"}, {file = "Brotli-1.0.9-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:2aad0e0baa04517741c9bb5b07586c642302e5fb3e75319cb62087bd0995ab19"}, @@ -938,9 +926,6 @@ brotli = [ {file = "Brotli-1.0.9-cp39-cp39-manylinux1_i686.whl", hash = "sha256:16d528a45c2e1909c2798f27f7bf0a3feec1dc9e50948e738b961618e38b6a7b"}, {file = "Brotli-1.0.9-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:56d027eace784738457437df7331965473f2c0da2c70e1a1f6fdbae5402e0389"}, {file = "Brotli-1.0.9-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9bf919756d25e4114ace16a8ce91eb340eb57a08e2c6950c3cebcbe3dff2a5e7"}, - {file = "Brotli-1.0.9-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:e4c4e92c14a57c9bd4cb4be678c25369bf7a092d55fd0866f759e425b9660806"}, - {file = "Brotli-1.0.9-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:e48f4234f2469ed012a98f4b7874e7f7e173c167bed4934912a29e03167cf6b1"}, - {file = "Brotli-1.0.9-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9ed4c92a0665002ff8ea852353aeb60d9141eb04109e88928026d3c8a9e5433c"}, {file = "Brotli-1.0.9-cp39-cp39-win32.whl", hash = "sha256:cfc391f4429ee0a9370aa93d812a52e1fee0f37a81861f4fdd1f4fb28e8547c3"}, {file = "Brotli-1.0.9-cp39-cp39-win_amd64.whl", hash = "sha256:854c33dad5ba0fbd6ab69185fec8dab89e13cda6b7d191ba111987df74f38761"}, {file = "Brotli-1.0.9-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:9749a124280a0ada4187a6cfd1ffd35c350fb3af79c706589d98e088c5044267"}, @@ -956,8 +941,8 @@ charset-normalizer = [ {file = "charset_normalizer-2.0.12-py3-none-any.whl", hash = "sha256:6881edbebdb17b39b4eaaa821b438bf6eddffb4468cf344f09f89def34a8b1df"}, ] click = [ - {file = "click-8.0.4-py3-none-any.whl", hash = "sha256:6a7a62563bbfabfda3a38f3023a1db4a35978c0abd76f6c9605ecd6554d6d9b1"}, - {file = "click-8.0.4.tar.gz", hash = "sha256:8458d7b1287c5fb128c90e23381cf99dcde74beaf6c7ff6384ce84d6fe090adb"}, + {file = "click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"}, + {file = "click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"}, ] colorama = [ {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"}, @@ -1036,8 +1021,8 @@ cython = [ {file = "Cython-0.29.28.tar.gz", hash = "sha256:d6fac2342802c30e51426828fe084ff4deb1b3387367cf98976bb2e64b6f8e45"}, ] filetype = [ - {file = "filetype-1.0.10-py2.py3-none-any.whl", hash = "sha256:63fbe6e818a3d1cfac1d62b196574a7a4b7fc8e06a6c500d53577c018ef127d9"}, - {file = "filetype-1.0.10.tar.gz", hash = "sha256:323a13500731b6c65a253bc3930bbce9a56dfba71e90b60ffd968ab69d9ae937"}, + {file = "filetype-1.0.13-py2.py3-none-any.whl", hash = "sha256:8f5d2d5ae7fec00c71dadfe8a747c2d6da91d77f9b4e550bbdb0881f63a07e43"}, + {file = "filetype-1.0.13.tar.gz", hash = "sha256:6a104762fe93d755c962aa96cb3d930a48f91a0761047126c5eead2153e33b03"}, ] greenlet = [ {file = "greenlet-1.1.2-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:58df5c2a0e293bf665a51f8a100d3e9956febfbf1d9aaf8c0677cf70218910c6"}, @@ -1109,8 +1094,8 @@ iniconfig = [ {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, ] jinja2 = [ - {file = "Jinja2-3.1.1-py3-none-any.whl", hash = "sha256:539835f51a74a69f41b848a9645dbdc35b4f20a3b601e2d9a7e22947b15ff119"}, - {file = "Jinja2-3.1.1.tar.gz", hash = "sha256:640bed4bb501cbd17194b3cace1dc2126f5b619cf068a726b98192a0fde74ae9"}, + {file = "Jinja2-3.1.2-py3-none-any.whl", hash = "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61"}, + {file = "Jinja2-3.1.2.tar.gz", hash = "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852"}, ] jmespath = [ {file = "jmespath-0.9.5-py2.py3-none-any.whl", hash = "sha256:695cb76fa78a10663425d5b73ddc5714eb711157e52704d69be03b1a02ba4fec"}, @@ -1219,12 +1204,12 @@ pymysql = [ {file = "PyMySQL-1.0.2.tar.gz", hash = "sha256:816927a350f38d56072aeca5dfb10221fe1dc653745853d30a216637f5d7ad36"}, ] pyparsing = [ - {file = "pyparsing-3.0.7-py3-none-any.whl", hash = "sha256:a6c06a88f252e6c322f65faf8f418b16213b51bdfaece0524c1c1bc30c63c484"}, - {file = "pyparsing-3.0.7.tar.gz", hash = "sha256:18ee9022775d270c55187733956460083db60b37d0d0fb357445f3094eed3eea"}, + {file = "pyparsing-3.0.8-py3-none-any.whl", hash = "sha256:ef7b523f6356f763771559412c0d7134753f037822dad1b16945b7b846f7ad06"}, + {file = "pyparsing-3.0.8.tar.gz", hash = "sha256:7bf433498c016c4314268d95df76c81b842a4cb2b276fa3312cfb1e1d85f6954"}, ] pytest = [ - {file = "pytest-7.1.1-py3-none-any.whl", hash = "sha256:92f723789a8fdd7180b6b06483874feca4c48a5c76968e03bb3e7f806a1869ea"}, - {file = "pytest-7.1.1.tar.gz", hash = "sha256:841132caef6b1ad17a9afde46dc4f6cfa59a05f9555aae5151f73bdf2820ca63"}, + {file = "pytest-7.1.2-py3-none-any.whl", hash = "sha256:13d0e3ccfc2b6e26be000cb6568c832ba67ba32e719443bfe725814d3c42433c"}, + {file = "pytest-7.1.2.tar.gz", hash = "sha256:a06a0425453864a270bc45e71f783330a7428defb4230fb5e6a731fde06ecd45"}, ] pytest-html = [ {file = "pytest-html-3.1.1.tar.gz", hash = "sha256:3ee1cf319c913d19fe53aeb0bc400e7b0bc2dbeb477553733db1dad12eb75ee3"}, @@ -1335,34 +1320,34 @@ tomli = [ {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, ] typed-ast = [ - {file = "typed_ast-1.5.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:183b183b7771a508395d2cbffd6db67d6ad52958a5fdc99f450d954003900266"}, - {file = "typed_ast-1.5.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:676d051b1da67a852c0447621fdd11c4e104827417bf216092ec3e286f7da596"}, - {file = "typed_ast-1.5.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bc2542e83ac8399752bc16e0b35e038bdb659ba237f4222616b4e83fb9654985"}, - {file = "typed_ast-1.5.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:74cac86cc586db8dfda0ce65d8bcd2bf17b58668dfcc3652762f3ef0e6677e76"}, - {file = "typed_ast-1.5.2-cp310-cp310-win_amd64.whl", hash = "sha256:18fe320f354d6f9ad3147859b6e16649a0781425268c4dde596093177660e71a"}, - {file = "typed_ast-1.5.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:31d8c6b2df19a777bc8826770b872a45a1f30cfefcfd729491baa5237faae837"}, - {file = "typed_ast-1.5.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:963a0ccc9a4188524e6e6d39b12c9ca24cc2d45a71cfdd04a26d883c922b4b78"}, - {file = "typed_ast-1.5.2-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:0eb77764ea470f14fcbb89d51bc6bbf5e7623446ac4ed06cbd9ca9495b62e36e"}, - {file = "typed_ast-1.5.2-cp36-cp36m-win_amd64.whl", hash = "sha256:294a6903a4d087db805a7656989f613371915fc45c8cc0ddc5c5a0a8ad9bea4d"}, - {file = "typed_ast-1.5.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:26a432dc219c6b6f38be20a958cbe1abffcc5492821d7e27f08606ef99e0dffd"}, - {file = "typed_ast-1.5.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c7407cfcad702f0b6c0e0f3e7ab876cd1d2c13b14ce770e412c0c4b9728a0f88"}, - {file = "typed_ast-1.5.2-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f30ddd110634c2d7534b2d4e0e22967e88366b0d356b24de87419cc4410c41b7"}, - {file = "typed_ast-1.5.2-cp37-cp37m-win_amd64.whl", hash = "sha256:8c08d6625bb258179b6e512f55ad20f9dfef019bbfbe3095247401e053a3ea30"}, - {file = "typed_ast-1.5.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:90904d889ab8e81a956f2c0935a523cc4e077c7847a836abee832f868d5c26a4"}, - {file = "typed_ast-1.5.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:bbebc31bf11762b63bf61aaae232becb41c5bf6b3461b80a4df7e791fabb3aca"}, - {file = "typed_ast-1.5.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c29dd9a3a9d259c9fa19d19738d021632d673f6ed9b35a739f48e5f807f264fb"}, - {file = "typed_ast-1.5.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:58ae097a325e9bb7a684572d20eb3e1809802c5c9ec7108e85da1eb6c1a3331b"}, - {file = "typed_ast-1.5.2-cp38-cp38-win_amd64.whl", hash = "sha256:da0a98d458010bf4fe535f2d1e367a2e2060e105978873c04c04212fb20543f7"}, - {file = "typed_ast-1.5.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:33b4a19ddc9fc551ebabca9765d54d04600c4a50eda13893dadf67ed81d9a098"}, - {file = "typed_ast-1.5.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1098df9a0592dd4c8c0ccfc2e98931278a6c6c53cb3a3e2cf7e9ee3b06153344"}, - {file = "typed_ast-1.5.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42c47c3b43fe3a39ddf8de1d40dbbfca60ac8530a36c9b198ea5b9efac75c09e"}, - {file = "typed_ast-1.5.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f290617f74a610849bd8f5514e34ae3d09eafd521dceaa6cf68b3f4414266d4e"}, - {file = "typed_ast-1.5.2-cp39-cp39-win_amd64.whl", hash = "sha256:df05aa5b241e2e8045f5f4367a9f6187b09c4cdf8578bb219861c4e27c443db5"}, - {file = "typed_ast-1.5.2.tar.gz", hash = "sha256:525a2d4088e70a9f75b08b3f87a51acc9cde640e19cc523c7e41aa355564ae27"}, + {file = "typed_ast-1.5.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9ad3b48cf2b487be140072fb86feff36801487d4abb7382bb1929aaac80638ea"}, + {file = "typed_ast-1.5.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:542cd732351ba8235f20faa0fc7398946fe1a57f2cdb289e5497e1e7f48cfedb"}, + {file = "typed_ast-1.5.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5dc2c11ae59003d4a26dda637222d9ae924387f96acae9492df663843aefad55"}, + {file = "typed_ast-1.5.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:fd5df1313915dbd70eaaa88c19030b441742e8b05e6103c631c83b75e0435ccc"}, + {file = "typed_ast-1.5.3-cp310-cp310-win_amd64.whl", hash = "sha256:e34f9b9e61333ecb0f7d79c21c28aa5cd63bec15cb7e1310d7d3da6ce886bc9b"}, + {file = "typed_ast-1.5.3-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f818c5b81966d4728fec14caa338e30a70dfc3da577984d38f97816c4b3071ec"}, + {file = "typed_ast-1.5.3-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3042bfc9ca118712c9809201f55355479cfcdc17449f9f8db5e744e9625c6805"}, + {file = "typed_ast-1.5.3-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:4fff9fdcce59dc61ec1b317bdb319f8f4e6b69ebbe61193ae0a60c5f9333dc49"}, + {file = "typed_ast-1.5.3-cp36-cp36m-win_amd64.whl", hash = "sha256:8e0b8528838ffd426fea8d18bde4c73bcb4167218998cc8b9ee0a0f2bfe678a6"}, + {file = "typed_ast-1.5.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8ef1d96ad05a291f5c36895d86d1375c0ee70595b90f6bb5f5fdbee749b146db"}, + {file = "typed_ast-1.5.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ed44e81517364cb5ba367e4f68fca01fba42a7a4690d40c07886586ac267d9b9"}, + {file = "typed_ast-1.5.3-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f60d9de0d087454c91b3999a296d0c4558c1666771e3460621875021bf899af9"}, + {file = "typed_ast-1.5.3-cp37-cp37m-win_amd64.whl", hash = "sha256:9e237e74fd321a55c90eee9bc5d44be976979ad38a29bbd734148295c1ce7617"}, + {file = "typed_ast-1.5.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ee852185964744987609b40aee1d2eb81502ae63ee8eef614558f96a56c1902d"}, + {file = "typed_ast-1.5.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:27e46cdd01d6c3a0dd8f728b6a938a6751f7bd324817501c15fb056307f918c6"}, + {file = "typed_ast-1.5.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d64dabc6336ddc10373922a146fa2256043b3b43e61f28961caec2a5207c56d5"}, + {file = "typed_ast-1.5.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:8cdf91b0c466a6c43f36c1964772918a2c04cfa83df8001ff32a89e357f8eb06"}, + {file = "typed_ast-1.5.3-cp38-cp38-win_amd64.whl", hash = "sha256:9cc9e1457e1feb06b075c8ef8aeb046a28ec351b1958b42c7c31c989c841403a"}, + {file = "typed_ast-1.5.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e20d196815eeffb3d76b75223e8ffed124e65ee62097e4e73afb5fec6b993e7a"}, + {file = "typed_ast-1.5.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:37e5349d1d5de2f4763d534ccb26809d1c24b180a477659a12c4bde9dd677d74"}, + {file = "typed_ast-1.5.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c9f1a27592fac87daa4e3f16538713d705599b0a27dfe25518b80b6b017f0a6d"}, + {file = "typed_ast-1.5.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:8831479695eadc8b5ffed06fdfb3e424adc37962a75925668deeb503f446c0a3"}, + {file = "typed_ast-1.5.3-cp39-cp39-win_amd64.whl", hash = "sha256:20d5118e494478ef2d3a2702d964dae830aedd7b4d3b626d003eea526be18718"}, + {file = "typed_ast-1.5.3.tar.gz", hash = "sha256:27f25232e2dd0edfe1f019d6bfaaf11e86e657d9bdb7b0956db95f560cceb2b3"}, ] typing-extensions = [ - {file = "typing_extensions-4.1.1-py3-none-any.whl", hash = "sha256:21c85e0fe4b9a155d0799430b0ad741cdce7e359660ccbd8b530613e8df88ce2"}, - {file = "typing_extensions-4.1.1.tar.gz", hash = "sha256:1a9462dcc3347a79b1f1c0271fbe79e844580bb598bafa1ed208b94da3cdcd42"}, + {file = "typing_extensions-4.2.0-py3-none-any.whl", hash = "sha256:6657594ee297170d19f67d55c05852a874e7eb634f4f753dbd667855e07c1708"}, + {file = "typing_extensions-4.2.0.tar.gz", hash = "sha256:f1c24655a0da0d1b67f07e17a5e6b2a105894e6824b92096378bb3668ef02376"}, ] urllib3 = [ {file = "urllib3-1.26.9-py2.py3-none-any.whl", hash = "sha256:44ece4d53fb1706f667c9bd1c648f5469a2ec925fcf3a776667042d645472c14"}, @@ -1373,6 +1358,6 @@ win32-setctime = [ {file = "win32_setctime-1.1.0.tar.gz", hash = "sha256:15cf5750465118d6929ae4de4eb46e8edae9a5634350c01ba582df868e932cb2"}, ] zipp = [ - {file = "zipp-3.7.0-py3-none-any.whl", hash = "sha256:b47250dd24f92b7dd6a0a8fc5244da14608f3ca90a5efcd37a3b1642fac9a375"}, - {file = "zipp-3.7.0.tar.gz", hash = "sha256:9f50f446828eb9d45b267433fd3e9da8d801f614129124863f9c51ebceafb87d"}, + {file = "zipp-3.8.0-py3-none-any.whl", hash = "sha256:c4f6e5bbf48e74f7a38e7cc5b0480ff42b0ae5178957d564d18932525d5cf099"}, + {file = "zipp-3.8.0.tar.gz", hash = "sha256:56bf8aadb83c24db6c4b577e13de374ccfb67da2078beba1d037c17980bf43ad"}, ] From fac9898100ba44323b89a8a993a876e71a0d856e Mon Sep 17 00:00:00 2001 From: debugtalk <mail@debugtalk.com> Date: Mon, 9 May 2022 15:53:41 +0800 Subject: [PATCH 029/109] feat: add pre-commit-hook --- Makefile | 5 +++ docs/CHANGELOG.md | 4 ++- httprunner/step.py | 2 +- httprunner/step_sql_request.py | 7 +++- httprunner/step_thrift_request.py | 25 +++++++------- scripts/install-pre-commit-hook | 54 +++++++++++++++++++++++++++++++ 6 files changed, 82 insertions(+), 15 deletions(-) create mode 100644 scripts/install-pre-commit-hook diff --git a/Makefile b/Makefile index e999b732..b094ea9c 100644 --- a/Makefile +++ b/Makefile @@ -18,6 +18,11 @@ build: ## build hrp cli tool @echo "[info] build hrp cli tool" @. scripts/build.sh +.PHONY: install-hooks +install-hooks: ## install git hooks + @find scripts -name "install-*-hook" | awk -F'-' '{s=$$2;for(i=3;i<NF;i++){s=s"-"$$i;}print s;}' | while read f; do bash "scripts/install-$$f-hook"; done + @echo "[OK] install all hooks" + .PHONY: help help: ## print make commands @grep -E '^[a-zA-Z0-9_-]+:.*?## .*$$' $(MAKEFILE_LIST) | \ diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 443c1552..9d28a731 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -1,6 +1,8 @@ # Release History -## v4.1.0-alpha (2022-05-07) +## v4.1.0-alpha (2022-05-09) + +- feat: add pre-commit-hook to format go/python code **go version** diff --git a/httprunner/step.py b/httprunner/step.py index 3b5f2535..7f0485a5 100644 --- a/httprunner/step.py +++ b/httprunner/step.py @@ -64,4 +64,4 @@ class Step(object): return self.__step.type() def run(self, runner: HttpRunner) -> StepResult: - return self.__step.run(runner) \ No newline at end of file + return self.__step.run(runner) diff --git a/httprunner/step_sql_request.py b/httprunner/step_sql_request.py index cc5ddfba..4b6fedc0 100644 --- a/httprunner/step_sql_request.py +++ b/httprunner/step_sql_request.py @@ -11,7 +11,11 @@ from httprunner.models import IStep, StepResult, TStep from httprunner.models import SqlMethodEnum, TSqlRequest from httprunner.response import SqlResponseObject from httprunner.runner import HttpRunner -from httprunner.step_request import (StepRequestExtraction, StepRequestValidation, call_hooks) +from httprunner.step_request import ( + StepRequestExtraction, + StepRequestValidation, + call_hooks, +) try: import sqlalchemy @@ -73,6 +77,7 @@ def run_step_sql_request(runner: HttpRunner, step: TStep) -> StepResult: if not runner.db_engine: ensure_sql_ready() from httprunner.database.engine import DBEngine + runner.db_engine = DBEngine( f'mysql+pymysql://{parsed_request_dict["db_config"]["user"]}:' f'{parsed_request_dict["db_config"]["password"]}@{parsed_request_dict["db_config"]["ip"]}:' diff --git a/httprunner/step_thrift_request.py b/httprunner/step_thrift_request.py index bef05448..ea7aadf7 100644 --- a/httprunner/step_thrift_request.py +++ b/httprunner/step_thrift_request.py @@ -66,30 +66,30 @@ def run_step_thrift_request(runner: HttpRunner, step: TStep) -> StepResult: parsed_request_dict["psm"] = parsed_request_dict["psm"] or config.thrift.psm parsed_request_dict["env"] = parsed_request_dict["env"] or config.thrift.env parsed_request_dict["cluster"] = ( - parsed_request_dict["cluster"] or config.thrift.cluster + parsed_request_dict["cluster"] or config.thrift.cluster ) parsed_request_dict["idl_path"] = ( - parsed_request_dict["idl_path"] or config.thrift.idl_path + parsed_request_dict["idl_path"] or config.thrift.idl_path ) parsed_request_dict["include_dirs"] = ( - parsed_request_dict["include_dirs"] or config.thrift.include_dirs + parsed_request_dict["include_dirs"] or config.thrift.include_dirs ) parsed_request_dict["method"] = ( - parsed_request_dict["method"] or config.thrift.method + parsed_request_dict["method"] or config.thrift.method ) parsed_request_dict["service_name"] = ( - parsed_request_dict["service_name"] or config.thrift.service_name + parsed_request_dict["service_name"] or config.thrift.service_name ) parsed_request_dict["ip"] = parsed_request_dict["ip"] or config.thrift.ip parsed_request_dict["port"] = parsed_request_dict["port"] or config.thrift.port parsed_request_dict["proto_type"] = ( - parsed_request_dict["proto_type"] or config.thrift.proto_type + parsed_request_dict["proto_type"] or config.thrift.proto_type ) parsed_request_dict["trans_port"] = ( - parsed_request_dict["trans_type"] or config.thrift.trans_type + parsed_request_dict["trans_type"] or config.thrift.trans_type ) parsed_request_dict["timeout"] = ( - parsed_request_dict["timeout"] or config.thrift.timeout + parsed_request_dict["timeout"] or config.thrift.timeout ) parsed_request_dict["thrift_client"] = parsed_request_dict["thrift_client"] @@ -105,6 +105,7 @@ def run_step_thrift_request(runner: HttpRunner, step: TStep) -> StepResult: if not runner.thrift_client: ensure_thrift_ready() from httprunner.thrift.thrift_client import ThriftClient + runner.thrift_client = ThriftClient( thrift_file=parsed_request_dict["idl_path"], service_name=parsed_request_dict["service_name"], @@ -214,7 +215,7 @@ class RunThriftRequest(IStep): return self def teardown_hook( - self, hook: Text, assign_var_name: Text = None + self, hook: Text, assign_var_name: Text = None ) -> "RunThriftRequest": if assign_var_name: self.__step.teardown_hooks.append({assign_var_name: hook}) @@ -224,7 +225,7 @@ class RunThriftRequest(IStep): return self def setup_hook( - self, hook: Text, assign_var_name: Text = None + self, hook: Text, assign_var_name: Text = None ) -> "RunThriftRequest": if assign_var_name: self.__step.setup_hooks.append({assign_var_name: hook}) @@ -247,7 +248,7 @@ class RunThriftRequest(IStep): return self def with_thrift_client( - self, thrift_client: Union["ThriftClient", str] + self, thrift_client: Union["ThriftClient", str] ) -> "RunThriftRequest": self.__step.thrift_request.thrift_client = thrift_client return self @@ -287,7 +288,7 @@ class RunThriftRequest(IStep): return StepThriftRequestValidation(self.__step) def with_jmespath( - self, jmes_path: Text, var_name: Text + self, jmes_path: Text, var_name: Text ) -> "StepThriftRequestExtraction": self.__step.extract[var_name] = jmes_path return StepThriftRequestExtraction(self.__step) diff --git a/scripts/install-pre-commit-hook b/scripts/install-pre-commit-hook new file mode 100644 index 00000000..6c47fa98 --- /dev/null +++ b/scripts/install-pre-commit-hook @@ -0,0 +1,54 @@ +#!/usr/bin/env bash + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)" +echo "SCRIPT_DIR:, $SCRIPT_DIR" +# assume the script is always in <repository>/scripts +pushd "$SCRIPT_DIR/.." >/dev/null + +PRE_COMMIT_FILE=.git/hooks/pre-commit + +# install pre-commit hook and make it executable +function install() { + go get mvdan.cc/gofumpt + go get github.com/incu6us/goimports-reviser/v2@latest + cat > $PRE_COMMIT_FILE <<'EOF' +#!/bin/bash + +# What does this script do? +# 1. gofumpt go files automatically +# 2. goimports-reviser go files automatically +# 3. black python files automatically + +# make sure gofumpt is installed +# What does each letter mean in "ACMRTUXB"? +# Added (A), Copied (C), Deleted (D), Modified (M), Renamed (R), have their type (i.e. regular file, symlink, +# submodule, ...) changed (T), are Unmerged (U), are Unknown (X), or have had their pairing Broken (B) +for file in $(git diff --name-only --cached --diff-filter=ACMRTUXB | grep '.go$') +do + echo "(gofumpt) $file" + gofumpt -w "$file" + echo "(goimports-reviser) $file" + goimports-reviser -file-path "$file" -rm-unused + git add "$file" +done + +for file in $(git diff --name-only --cached --diff-filter=ACMRTUXB | grep '.py$') +do + echo "(black) $file" + black "$file" + git add "$file" +done +EOF + + chmod +x $PRE_COMMIT_FILE +} + +if [[ -f $PRE_COMMIT_FILE ]]; then + echo "Backing up $PRE_COMMIT_FILE to ${PRE_COMMIT_FILE}.bak" + mv $PRE_COMMIT_FILE ${PRE_COMMIT_FILE}.bak + install +else + install +fi + +popd >/dev/null \ No newline at end of file From 8d8a658508b19c2a1699fade8117f39eb48e78c5 Mon Sep 17 00:00:00 2001 From: buyuxiang <347586493@qq.com> Date: Mon, 9 May 2022 18:15:28 +0800 Subject: [PATCH 030/109] fix: disable keep alive --- hrp/boomer.go | 8 +++++--- hrp/cmd/boom.go | 1 + 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/hrp/boomer.go b/hrp/boomer.go index 377e61f3..c47db199 100644 --- a/hrp/boomer.go +++ b/hrp/boomer.go @@ -19,9 +19,6 @@ func NewBoomer(spawnCount int, spawnRate float64) *HRPBoomer { } b.hrpRunner = NewRunner(nil) - // set client transport for high concurrency load testing - b.hrpRunner.SetClientTransport(b.GetSpawnCount(), b.GetDisableKeepAlive(), b.GetDisableCompression()) - return b } @@ -32,6 +29,11 @@ type HRPBoomer struct { pluginsMutex *sync.RWMutex // avoid data race } +func (b *HRPBoomer) SetClientTransport() { + // set client transport for high concurrency load testing + b.hrpRunner.SetClientTransport(b.GetSpawnCount(), b.GetDisableKeepAlive(), b.GetDisableCompression()) +} + // Run starts to run load test for one or multiple testcases. func (b *HRPBoomer) Run(testcases ...ITestCase) { event := sdk.EventTracking{ diff --git a/hrp/cmd/boom.go b/hrp/cmd/boom.go index 2751ab52..1350b981 100644 --- a/hrp/cmd/boom.go +++ b/hrp/cmd/boom.go @@ -41,6 +41,7 @@ var boomCmd = &cobra.Command{ } hrpBoomer.SetDisableKeepAlive(disableKeepalive) hrpBoomer.SetDisableCompression(disableCompression) + hrpBoomer.SetClientTransport() hrpBoomer.EnableCPUProfile(cpuProfile, cpuProfileDuration) hrpBoomer.EnableMemoryProfile(memoryProfile, memoryProfileDuration) hrpBoomer.EnableGracefulQuit() From 17c7699ceed20443d47b4eefd19f3dce1b3d129c Mon Sep 17 00:00:00 2001 From: debugtalk <mail@debugtalk.com> Date: Mon, 9 May 2022 18:28:03 +0800 Subject: [PATCH 031/109] change: update image links --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index c63561ce..0581a634 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ > HttpRunner [用户调研问卷][survey] 持续收集中,我们将基于用户反馈动态调整产品特性和需求优先级。 -![flow chart](docs/assets/hrp-flow.jpg) +![flow chart](https://httprunner.com/image/hrp-flow.jpg) [版本发布日志] | [English] @@ -101,7 +101,7 @@ Use "hrp [command] --help" for more information about a command. ### 金牌赞助商 -[<img src="docs/assets/hogwarts.jpeg" alt="霍格沃兹测试开发学社" width="400">](https://ceshiren.com/) +[<img src="https://httprunner.com/image/hogwarts.jpeg" alt="霍格沃兹测试开发学社" width="400">](https://ceshiren.com/) > [霍格沃兹测试开发学社](http://qrcode.testing-studio.com/f?from=httprunner&url=https://ceshiren.com)是业界领先的测试开发技术高端教育品牌,隶属于[测吧(北京)科技有限公司](http://qrcode.testing-studio.com/f?from=httprunner&url=https://www.testing-studio.com) 。学院课程由一线大厂测试经理与资深测试开发专家参与研发,实战驱动。课程涵盖 web/app 自动化测试、接口测试、性能测试、安全测试、持续集成/持续交付/DevOps,测试左移&右移、精准测试、测试平台开发、测试管理等内容,帮助测试工程师实现测试开发技术转型。通过优秀的学社制度(奖学金、内推返学费、行业竞赛等多种方式)来实现学员、学社及用人企业的三方共赢。 @@ -109,7 +109,7 @@ Use "hrp [command] --help" for more information about a command. ### 开源服务赞助商 -[<img src="docs/assets/sentry-logo-black.svg" alt="Sentry" width="150">](https://sentry.io/_/open-source/) +[<img src="https://httprunner.com/image/sentry-logo-black.svg" alt="Sentry" width="150">](https://sentry.io/_/open-source/) HttpRunner is in Sentry Sponsored plan. @@ -117,7 +117,7 @@ HttpRunner is in Sentry Sponsored plan. 关注 HttpRunner 的微信公众号,第一时间获得最新资讯。 -<img src="docs/assets/qrcode.jpg" alt="HttpRunner" width="200"> +<img src="https://httprunner.com/image/qrcode.jpg" alt="HttpRunner" width="200"> 如果你期望加入 HttpRunner 核心用户群,请填写[用户调研问卷][survey]并留下你的联系方式,作者将拉你进群。 From 24effeccdba35ac364cbfbeb8788d775efa250f9 Mon Sep 17 00:00:00 2001 From: debugtalk <mail@debugtalk.com> Date: Mon, 9 May 2022 18:49:24 +0800 Subject: [PATCH 032/109] fix #1288: unable to go get httprunner v4 --- docs/CHANGELOG.md | 1 + docs/cmd/hrp.md | 2 +- docs/cmd/hrp_boom.md | 2 +- docs/cmd/hrp_convert.md | 2 +- docs/cmd/hrp_har2case.md | 2 +- docs/cmd/hrp_pytest.md | 2 +- docs/cmd/hrp_run.md | 2 +- docs/cmd/hrp_startproject.md | 2 +- go.mod | 2 +- hrp/boomer.go | 6 +++--- hrp/cmd/boom.go | 4 ++-- hrp/cmd/cli/main.go | 2 +- hrp/cmd/convert.go | 2 +- hrp/cmd/har2case.go | 2 +- hrp/cmd/pytest.go | 2 +- hrp/cmd/root.go | 4 ++-- hrp/cmd/run.go | 2 +- hrp/cmd/scaffold.go | 2 +- hrp/config.go | 6 ++---- hrp/internal/boomer/output.go | 7 ++----- hrp/internal/boomer/stats.go | 2 +- hrp/internal/builtin/utils.go | 8 ++++---- hrp/internal/convert/main.go | 8 ++++---- hrp/internal/har2case/core.go | 8 ++++---- hrp/internal/har2case/core_test.go | 11 +++++++---- hrp/internal/pytest/main.go | 6 +++--- hrp/internal/scaffold/main.go | 6 +++--- hrp/internal/sdk/events.go | 2 +- hrp/internal/sdk/init.go | 2 +- hrp/parser.go | 6 +++--- hrp/plugin.go | 2 +- hrp/response.go | 6 +++--- hrp/runner.go | 2 +- hrp/runner_test.go | 4 ++-- hrp/step_api.go | 2 +- hrp/step_request.go | 6 +++--- hrp/step_thinktime.go | 3 ++- hrp/step_websocket.go | 12 +++++++----- hrp/summary.go | 7 ++++--- hrp/testcase.go | 3 ++- hrp/testcase_test.go | 6 ++---- hrp/tests/extract_test.go | 2 +- hrp/tests/function_test.go | 2 +- hrp/tests/protocol_test.go | 2 +- hrp/tests/rendezvous_test.go | 2 +- hrp/tests/request_test.go | 2 +- hrp/tests/validate_test.go | 2 +- hrp/tests/variables_test.go | 2 +- 48 files changed, 92 insertions(+), 90 deletions(-) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 9d28a731..b3dc23dd 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -7,6 +7,7 @@ **go version** - fix: step request elapsed timing should contain ContentTransfer part +- fix #1288: unable to go get httprunner v4 **python version** diff --git a/docs/cmd/hrp.md b/docs/cmd/hrp.md index 1b512dba..2620d07f 100644 --- a/docs/cmd/hrp.md +++ b/docs/cmd/hrp.md @@ -36,4 +36,4 @@ Copyright 2017 debugtalk * [hrp run](hrp_run.md) - run API test with go engine * [hrp startproject](hrp_startproject.md) - create a scaffold project -###### Auto generated by spf13/cobra on 5-May-2022 +###### Auto generated by spf13/cobra on 9-May-2022 diff --git a/docs/cmd/hrp_boom.md b/docs/cmd/hrp_boom.md index dc049340..ad27f7b2 100644 --- a/docs/cmd/hrp_boom.md +++ b/docs/cmd/hrp_boom.md @@ -41,4 +41,4 @@ hrp boom [flags] * [hrp](hrp.md) - Next-Generation API Testing Solution. -###### Auto generated by spf13/cobra on 5-May-2022 +###### Auto generated by spf13/cobra on 9-May-2022 diff --git a/docs/cmd/hrp_convert.md b/docs/cmd/hrp_convert.md index 5366a607..7390e9cc 100644 --- a/docs/cmd/hrp_convert.md +++ b/docs/cmd/hrp_convert.md @@ -18,4 +18,4 @@ hrp convert $path... [flags] * [hrp](hrp.md) - Next-Generation API Testing Solution. -###### Auto generated by spf13/cobra on 5-May-2022 +###### Auto generated by spf13/cobra on 9-May-2022 diff --git a/docs/cmd/hrp_har2case.md b/docs/cmd/hrp_har2case.md index 852c2bc2..db6b8b10 100644 --- a/docs/cmd/hrp_har2case.md +++ b/docs/cmd/hrp_har2case.md @@ -24,4 +24,4 @@ hrp har2case $har_path... [flags] * [hrp](hrp.md) - Next-Generation API Testing Solution. -###### Auto generated by spf13/cobra on 5-May-2022 +###### Auto generated by spf13/cobra on 9-May-2022 diff --git a/docs/cmd/hrp_pytest.md b/docs/cmd/hrp_pytest.md index 6bc851e2..b2217ca1 100644 --- a/docs/cmd/hrp_pytest.md +++ b/docs/cmd/hrp_pytest.md @@ -16,4 +16,4 @@ hrp pytest $path ... [flags] * [hrp](hrp.md) - Next-Generation API Testing Solution. -###### Auto generated by spf13/cobra on 5-May-2022 +###### Auto generated by spf13/cobra on 9-May-2022 diff --git a/docs/cmd/hrp_run.md b/docs/cmd/hrp_run.md index d0125914..6ffdd6d2 100644 --- a/docs/cmd/hrp_run.md +++ b/docs/cmd/hrp_run.md @@ -35,4 +35,4 @@ hrp run $path... [flags] * [hrp](hrp.md) - Next-Generation API Testing Solution. -###### Auto generated by spf13/cobra on 5-May-2022 +###### Auto generated by spf13/cobra on 9-May-2022 diff --git a/docs/cmd/hrp_startproject.md b/docs/cmd/hrp_startproject.md index d459b838..4987cd6d 100644 --- a/docs/cmd/hrp_startproject.md +++ b/docs/cmd/hrp_startproject.md @@ -20,4 +20,4 @@ hrp startproject $project_name [flags] * [hrp](hrp.md) - Next-Generation API Testing Solution. -###### Auto generated by spf13/cobra on 5-May-2022 +###### Auto generated by spf13/cobra on 9-May-2022 diff --git a/go.mod b/go.mod index dbe79c4c..5dc2859b 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module github.com/httprunner/httprunner +module github.com/httprunner/httprunner/v4 go 1.16 diff --git a/hrp/boomer.go b/hrp/boomer.go index 377e61f3..05bee5ef 100644 --- a/hrp/boomer.go +++ b/hrp/boomer.go @@ -5,11 +5,11 @@ import ( "sync" "time" + "github.com/httprunner/funplugin" "github.com/rs/zerolog/log" - "github.com/httprunner/funplugin" - "github.com/httprunner/httprunner/hrp/internal/boomer" - "github.com/httprunner/httprunner/hrp/internal/sdk" + "github.com/httprunner/httprunner/v4/hrp/internal/boomer" + "github.com/httprunner/httprunner/v4/hrp/internal/sdk" ) func NewBoomer(spawnCount int, spawnRate float64) *HRPBoomer { diff --git a/hrp/cmd/boom.go b/hrp/cmd/boom.go index 2751ab52..53214371 100644 --- a/hrp/cmd/boom.go +++ b/hrp/cmd/boom.go @@ -5,8 +5,8 @@ import ( "github.com/spf13/cobra" - "github.com/httprunner/httprunner/hrp" - "github.com/httprunner/httprunner/hrp/internal/boomer" + "github.com/httprunner/httprunner/v4/hrp" + "github.com/httprunner/httprunner/v4/hrp/internal/boomer" ) // boomCmd represents the boom command diff --git a/hrp/cmd/cli/main.go b/hrp/cmd/cli/main.go index f5c1010c..63f5c9bd 100644 --- a/hrp/cmd/cli/main.go +++ b/hrp/cmd/cli/main.go @@ -5,7 +5,7 @@ import ( "github.com/getsentry/sentry-go" - "github.com/httprunner/httprunner/hrp/cmd" + "github.com/httprunner/httprunner/v4/hrp/cmd" ) func main() { diff --git a/hrp/cmd/convert.go b/hrp/cmd/convert.go index 7ec89187..0247d147 100644 --- a/hrp/cmd/convert.go +++ b/hrp/cmd/convert.go @@ -7,7 +7,7 @@ import ( "github.com/rs/zerolog/log" "github.com/spf13/cobra" - "github.com/httprunner/httprunner/hrp/internal/convert" + "github.com/httprunner/httprunner/v4/hrp/internal/convert" ) var convertCmd = &cobra.Command{ diff --git a/hrp/cmd/har2case.go b/hrp/cmd/har2case.go index ee8380c1..eecd40cc 100644 --- a/hrp/cmd/har2case.go +++ b/hrp/cmd/har2case.go @@ -6,7 +6,7 @@ import ( "github.com/rs/zerolog/log" "github.com/spf13/cobra" - "github.com/httprunner/httprunner/hrp/internal/har2case" + "github.com/httprunner/httprunner/v4/hrp/internal/har2case" ) // har2caseCmd represents the har2case command diff --git a/hrp/cmd/pytest.go b/hrp/cmd/pytest.go index 8a5b4c5a..fd7732fb 100644 --- a/hrp/cmd/pytest.go +++ b/hrp/cmd/pytest.go @@ -3,7 +3,7 @@ package cmd import ( "github.com/spf13/cobra" - "github.com/httprunner/httprunner/hrp/internal/pytest" + "github.com/httprunner/httprunner/v4/hrp/internal/pytest" ) var pytestCmd = &cobra.Command{ diff --git a/hrp/cmd/root.go b/hrp/cmd/root.go index c19775d9..2909ea15 100644 --- a/hrp/cmd/root.go +++ b/hrp/cmd/root.go @@ -9,7 +9,7 @@ import ( "github.com/rs/zerolog/log" "github.com/spf13/cobra" - "github.com/httprunner/httprunner/hrp/internal/version" + "github.com/httprunner/httprunner/v4/hrp/internal/version" ) // rootCmd represents the base command when called without any subcommands @@ -33,7 +33,7 @@ Website: https://httprunner.com Github: https://github.com/httprunner/httprunner Copyright 2017 debugtalk`, PersistentPreRun: func(cmd *cobra.Command, args []string) { - var noColor = false + noColor := false if runtime.GOOS == "windows" { noColor = true } diff --git a/hrp/cmd/run.go b/hrp/cmd/run.go index 891563fd..500e595b 100644 --- a/hrp/cmd/run.go +++ b/hrp/cmd/run.go @@ -5,7 +5,7 @@ import ( "github.com/spf13/cobra" - "github.com/httprunner/httprunner/hrp" + "github.com/httprunner/httprunner/v4/hrp" ) // runCmd represents the run command diff --git a/hrp/cmd/scaffold.go b/hrp/cmd/scaffold.go index 5f9eb945..f3441b82 100644 --- a/hrp/cmd/scaffold.go +++ b/hrp/cmd/scaffold.go @@ -7,7 +7,7 @@ import ( "github.com/rs/zerolog/log" "github.com/spf13/cobra" - "github.com/httprunner/httprunner/hrp/internal/scaffold" + "github.com/httprunner/httprunner/v4/hrp/internal/scaffold" ) var scaffoldCmd = &cobra.Command{ diff --git a/hrp/config.go b/hrp/config.go index 2b84d576..af903bc5 100644 --- a/hrp/config.go +++ b/hrp/config.go @@ -3,7 +3,7 @@ package hrp import ( "reflect" - "github.com/httprunner/httprunner/hrp/internal/builtin" + "github.com/httprunner/httprunner/v4/hrp/internal/builtin" ) // NewConfig returns a new constructed testcase config with specified testcase name. @@ -161,6 +161,4 @@ const ( thinkTimeDefaultMultiply = 1 ) -var ( - thinkTimeDefaultRandom = map[string]float64{"min_percentage": 0.5, "max_percentage": 1.5} -) +var thinkTimeDefaultRandom = map[string]float64{"min_percentage": 0.5, "max_percentage": 1.5} diff --git a/hrp/internal/boomer/output.go b/hrp/internal/boomer/output.go index 79284cf7..41bb2df6 100644 --- a/hrp/internal/boomer/output.go +++ b/hrp/internal/boomer/output.go @@ -14,7 +14,7 @@ import ( "github.com/prometheus/client_golang/prometheus/push" "github.com/rs/zerolog/log" - "github.com/httprunner/httprunner/hrp/internal/json" + "github.com/httprunner/httprunner/v4/hrp/internal/json" ) // Output is primarily responsible for printing test results to different destinations @@ -37,8 +37,7 @@ type Output interface { } // ConsoleOutput is the default output for standalone mode. -type ConsoleOutput struct { -} +type ConsoleOutput struct{} // NewConsoleOutput returns a ConsoleOutput. func NewConsoleOutput() *ConsoleOutput { @@ -102,12 +101,10 @@ func getTotalFailRatio(totalRequests, totalFailures int64) (failRatio float64) { // OnStart of ConsoleOutput has nothing to do. func (o *ConsoleOutput) OnStart() { - } // OnStop of ConsoleOutput has nothing to do. func (o *ConsoleOutput) OnStop() { - } // OnEvent will print to the console. diff --git a/hrp/internal/boomer/stats.go b/hrp/internal/boomer/stats.go index b15c655d..7a126006 100644 --- a/hrp/internal/boomer/stats.go +++ b/hrp/internal/boomer/stats.go @@ -3,7 +3,7 @@ package boomer import ( "time" - "github.com/httprunner/httprunner/hrp/internal/json" + "github.com/httprunner/httprunner/v4/hrp/internal/json" ) type transaction struct { diff --git a/hrp/internal/builtin/utils.go b/hrp/internal/builtin/utils.go index 98c63c6f..cacad024 100644 --- a/hrp/internal/builtin/utils.go +++ b/hrp/internal/builtin/utils.go @@ -13,12 +13,12 @@ import ( "strconv" "strings" + "github.com/httprunner/funplugin/shared" "github.com/pkg/errors" "github.com/rs/zerolog/log" "gopkg.in/yaml.v3" - "github.com/httprunner/funplugin/shared" - "github.com/httprunner/httprunner/hrp/internal/json" + "github.com/httprunner/httprunner/v4/hrp/internal/json" ) func Dump2JSON(data interface{}, path string) error { @@ -29,7 +29,7 @@ func Dump2JSON(data interface{}, path string) error { } log.Info().Str("path", path).Msg("dump data to json") file, _ := json.MarshalIndent(data, "", " ") - err = os.WriteFile(path, file, 0644) + err = os.WriteFile(path, file, 0o644) if err != nil { log.Error().Err(err).Msg("dump json path failed") return err @@ -56,7 +56,7 @@ func Dump2YAML(data interface{}, path string) error { return err } - err = os.WriteFile(path, buffer.Bytes(), 0644) + err = os.WriteFile(path, buffer.Bytes(), 0o644) if err != nil { log.Error().Err(err).Msg("dump yaml path failed") return err diff --git a/hrp/internal/convert/main.go b/hrp/internal/convert/main.go index c871c55e..ea58dd6e 100644 --- a/hrp/internal/convert/main.go +++ b/hrp/internal/convert/main.go @@ -7,10 +7,10 @@ import ( "github.com/rs/zerolog/log" - "github.com/httprunner/httprunner/hrp" - "github.com/httprunner/httprunner/hrp/internal/builtin" - "github.com/httprunner/httprunner/hrp/internal/sdk" - "github.com/httprunner/httprunner/hrp/internal/version" + "github.com/httprunner/httprunner/v4/hrp" + "github.com/httprunner/httprunner/v4/hrp/internal/builtin" + "github.com/httprunner/httprunner/v4/hrp/internal/sdk" + "github.com/httprunner/httprunner/v4/hrp/internal/version" ) func Convert2TestScripts(destType string, paths ...string) error { diff --git a/hrp/internal/har2case/core.go b/hrp/internal/har2case/core.go index d3772dd2..25824855 100644 --- a/hrp/internal/har2case/core.go +++ b/hrp/internal/har2case/core.go @@ -11,10 +11,10 @@ import ( "github.com/pkg/errors" "github.com/rs/zerolog/log" - "github.com/httprunner/httprunner/hrp" - "github.com/httprunner/httprunner/hrp/internal/builtin" - "github.com/httprunner/httprunner/hrp/internal/json" - "github.com/httprunner/httprunner/hrp/internal/sdk" + "github.com/httprunner/httprunner/v4/hrp" + "github.com/httprunner/httprunner/v4/hrp/internal/builtin" + "github.com/httprunner/httprunner/v4/hrp/internal/json" + "github.com/httprunner/httprunner/v4/hrp/internal/sdk" ) const ( diff --git a/hrp/internal/har2case/core_test.go b/hrp/internal/har2case/core_test.go index 25779e12..de2ee910 100644 --- a/hrp/internal/har2case/core_test.go +++ b/hrp/internal/har2case/core_test.go @@ -5,7 +5,7 @@ import ( "github.com/stretchr/testify/assert" - "github.com/httprunner/httprunner/hrp" + "github.com/httprunner/httprunner/v4/hrp" ) var ( @@ -348,7 +348,8 @@ func TestMakeValidate(t *testing.T) { Check: "status_code", Expect: 200, Assert: "equals", - Message: "assert response status code"}) { + Message: "assert response status code", + }) { t.Fatal() } @@ -361,7 +362,8 @@ func TestMakeValidate(t *testing.T) { Check: "headers.\"Content-Type\"", Expect: "application/json; charset=utf-8", Assert: "equals", - Message: "assert response header Content-Type"}) { + Message: "assert response header Content-Type", + }) { t.Fatal() } @@ -374,7 +376,8 @@ func TestMakeValidate(t *testing.T) { Check: "body.Code", Expect: float64(200), // TODO Assert: "equals", - Message: "assert response body Code"}) { + Message: "assert response body Code", + }) { t.Fatal() } } diff --git a/hrp/internal/pytest/main.go b/hrp/internal/pytest/main.go index 7e749d4a..a1c0c88c 100644 --- a/hrp/internal/pytest/main.go +++ b/hrp/internal/pytest/main.go @@ -3,9 +3,9 @@ package pytest import ( "fmt" - "github.com/httprunner/httprunner/hrp/internal/builtin" - "github.com/httprunner/httprunner/hrp/internal/sdk" - "github.com/httprunner/httprunner/hrp/internal/version" + "github.com/httprunner/httprunner/v4/hrp/internal/builtin" + "github.com/httprunner/httprunner/v4/hrp/internal/sdk" + "github.com/httprunner/httprunner/v4/hrp/internal/version" ) func RunPytest(args []string) error { diff --git a/hrp/internal/scaffold/main.go b/hrp/internal/scaffold/main.go index d655384d..c4824f8b 100644 --- a/hrp/internal/scaffold/main.go +++ b/hrp/internal/scaffold/main.go @@ -7,12 +7,12 @@ import ( "os/exec" "path/filepath" + "github.com/httprunner/funplugin/shared" "github.com/pkg/errors" "github.com/rs/zerolog/log" - "github.com/httprunner/funplugin/shared" - "github.com/httprunner/httprunner/hrp/internal/builtin" - "github.com/httprunner/httprunner/hrp/internal/sdk" + "github.com/httprunner/httprunner/v4/hrp/internal/builtin" + "github.com/httprunner/httprunner/v4/hrp/internal/sdk" ) type PluginType string diff --git a/hrp/internal/sdk/events.go b/hrp/internal/sdk/events.go index 4d957455..e2667458 100644 --- a/hrp/internal/sdk/events.go +++ b/hrp/internal/sdk/events.go @@ -5,7 +5,7 @@ import ( "net/url" "time" - "github.com/httprunner/httprunner/hrp/internal/version" + "github.com/httprunner/httprunner/v4/hrp/internal/version" ) type IEvent interface { diff --git a/hrp/internal/sdk/init.go b/hrp/internal/sdk/init.go index ae80d920..8e69d5e2 100644 --- a/hrp/internal/sdk/init.go +++ b/hrp/internal/sdk/init.go @@ -9,7 +9,7 @@ import ( "github.com/google/uuid" "github.com/rs/zerolog/log" - "github.com/httprunner/httprunner/hrp/internal/version" + "github.com/httprunner/httprunner/v4/hrp/internal/version" ) const ( diff --git a/hrp/parser.go b/hrp/parser.go index 3a0d03bb..b9693f0d 100644 --- a/hrp/parser.go +++ b/hrp/parser.go @@ -9,12 +9,12 @@ import ( "regexp" "strings" + "github.com/httprunner/funplugin" + "github.com/httprunner/funplugin/shared" "github.com/maja42/goval" "github.com/rs/zerolog/log" - "github.com/httprunner/funplugin" - "github.com/httprunner/funplugin/shared" - "github.com/httprunner/httprunner/hrp/internal/builtin" + "github.com/httprunner/httprunner/v4/hrp/internal/builtin" ) func newParser() *Parser { diff --git a/hrp/plugin.go b/hrp/plugin.go index a8144797..7929d030 100644 --- a/hrp/plugin.go +++ b/hrp/plugin.go @@ -10,7 +10,7 @@ import ( "github.com/httprunner/funplugin" "github.com/rs/zerolog/log" - "github.com/httprunner/httprunner/hrp/internal/sdk" + "github.com/httprunner/httprunner/v4/hrp/internal/sdk" ) const ( diff --git a/hrp/response.go b/hrp/response.go index dbfd3edb..33b363cb 100644 --- a/hrp/response.go +++ b/hrp/response.go @@ -14,8 +14,8 @@ import ( "github.com/pkg/errors" "github.com/rs/zerolog/log" - "github.com/httprunner/httprunner/hrp/internal/builtin" - "github.com/httprunner/httprunner/hrp/internal/json" + "github.com/httprunner/httprunner/v4/hrp/internal/builtin" + "github.com/httprunner/httprunner/v4/hrp/internal/json" ) var fieldTags = []string{"proto", "status_code", "headers", "cookies", "body", textExtractorSubRegexp} @@ -268,7 +268,7 @@ func (v *responseObject) searchRegexp(expr string) interface{} { } match := regexpCompile.FindStringSubmatch(bodyStr) if len(match) > 1 { - return match[1] //return first matched result in parentheses + return match[1] // return first matched result in parentheses } log.Error().Str("expr", expr).Msg("search regexp failed") return expr diff --git a/hrp/runner.go b/hrp/runner.go index 74c463cd..62a9a6b7 100644 --- a/hrp/runner.go +++ b/hrp/runner.go @@ -14,7 +14,7 @@ import ( "github.com/rs/zerolog/log" "golang.org/x/net/http2" - "github.com/httprunner/httprunner/hrp/internal/sdk" + "github.com/httprunner/httprunner/v4/hrp/internal/sdk" ) // Run starts to run API test with default configs. diff --git a/hrp/runner_test.go b/hrp/runner_test.go index 25eaca68..7ba1f4c2 100644 --- a/hrp/runner_test.go +++ b/hrp/runner_test.go @@ -8,8 +8,8 @@ import ( "github.com/rs/zerolog/log" "github.com/stretchr/testify/assert" - "github.com/httprunner/httprunner/hrp/internal/builtin" - "github.com/httprunner/httprunner/hrp/internal/scaffold" + "github.com/httprunner/httprunner/v4/hrp/internal/builtin" + "github.com/httprunner/httprunner/v4/hrp/internal/scaffold" ) func buildHashicorpGoPlugin() { diff --git a/hrp/step_api.go b/hrp/step_api.go index 292a616f..1c9992ba 100644 --- a/hrp/step_api.go +++ b/hrp/step_api.go @@ -3,7 +3,7 @@ package hrp import ( "fmt" - "github.com/httprunner/httprunner/hrp/internal/builtin" + "github.com/httprunner/httprunner/v4/hrp/internal/builtin" ) // IAPI represents interface for api, diff --git a/hrp/step_request.go b/hrp/step_request.go index 118be910..d4e17e99 100644 --- a/hrp/step_request.go +++ b/hrp/step_request.go @@ -20,9 +20,9 @@ import ( "github.com/pkg/errors" "github.com/rs/zerolog/log" - "github.com/httprunner/httprunner/hrp/internal/builtin" - "github.com/httprunner/httprunner/hrp/internal/httpstat" - "github.com/httprunner/httprunner/hrp/internal/json" + "github.com/httprunner/httprunner/v4/hrp/internal/builtin" + "github.com/httprunner/httprunner/v4/hrp/internal/httpstat" + "github.com/httprunner/httprunner/v4/hrp/internal/json" ) type HTTPMethod string diff --git a/hrp/step_thinktime.go b/hrp/step_thinktime.go index 9abb20d5..6b14b462 100644 --- a/hrp/step_thinktime.go +++ b/hrp/step_thinktime.go @@ -3,8 +3,9 @@ package hrp import ( "time" - "github.com/httprunner/httprunner/hrp/internal/builtin" "github.com/rs/zerolog/log" + + "github.com/httprunner/httprunner/v4/hrp/internal/builtin" ) type ThinkTime struct { diff --git a/hrp/step_websocket.go b/hrp/step_websocket.go index 0db5ffae..f3a788fd 100644 --- a/hrp/step_websocket.go +++ b/hrp/step_websocket.go @@ -3,15 +3,17 @@ package hrp import ( "bytes" "fmt" - "github.com/gorilla/websocket" - "github.com/httprunner/httprunner/hrp/internal/builtin" - "github.com/httprunner/httprunner/hrp/internal/json" - "github.com/pkg/errors" - "github.com/rs/zerolog/log" "net/http" "testing" "time" "unsafe" + + "github.com/gorilla/websocket" + "github.com/pkg/errors" + "github.com/rs/zerolog/log" + + "github.com/httprunner/httprunner/v4/hrp/internal/builtin" + "github.com/httprunner/httprunner/v4/hrp/internal/json" ) const ( diff --git a/hrp/summary.go b/hrp/summary.go index 42dc37d7..13e6085a 100644 --- a/hrp/summary.go +++ b/hrp/summary.go @@ -10,9 +10,10 @@ import ( "runtime" "time" - "github.com/httprunner/httprunner/hrp/internal/builtin" - "github.com/httprunner/httprunner/hrp/internal/version" "github.com/rs/zerolog/log" + + "github.com/httprunner/httprunner/v4/hrp/internal/builtin" + "github.com/httprunner/httprunner/v4/hrp/internal/version" ) func newOutSummary() *Summary { @@ -72,7 +73,7 @@ func (s *Summary) genHTMLReport() error { } reportPath := filepath.Join(reportsDir, fmt.Sprintf("report-%v.html", s.Time.StartAt.Unix())) - file, err := os.OpenFile(reportPath, os.O_WRONLY|os.O_CREATE, 0666) + file, err := os.OpenFile(reportPath, os.O_WRONLY|os.O_CREATE, 0o666) if err != nil { log.Error().Err(err).Msg("open file failed") return err diff --git a/hrp/testcase.go b/hrp/testcase.go index ce6a8106..d9a241de 100644 --- a/hrp/testcase.go +++ b/hrp/testcase.go @@ -7,9 +7,10 @@ import ( "path/filepath" "strings" - "github.com/httprunner/httprunner/hrp/internal/builtin" "github.com/pkg/errors" "github.com/rs/zerolog/log" + + "github.com/httprunner/httprunner/v4/hrp/internal/builtin" ) // ITestCase represents interface for testcases, diff --git a/hrp/testcase_test.go b/hrp/testcase_test.go index e49eccfe..eb6a69ae 100644 --- a/hrp/testcase_test.go +++ b/hrp/testcase_test.go @@ -5,7 +5,7 @@ import ( "github.com/stretchr/testify/assert" - "github.com/httprunner/httprunner/hrp/internal/builtin" + "github.com/httprunner/httprunner/v4/hrp/internal/builtin" ) const ( @@ -22,9 +22,7 @@ var ( demoAPIGETPath APIPath = templatesDir + "/api/get.yml" ) -var ( - demoTestCaseWithThinkTimePath TestCasePath = hrpExamplesDir + "/think_time_test.json" -) +var demoTestCaseWithThinkTimePath TestCasePath = hrpExamplesDir + "/think_time_test.json" var demoTestCaseWithPlugin = &TestCase{ Config: NewConfig("demo with complex mechanisms"). diff --git a/hrp/tests/extract_test.go b/hrp/tests/extract_test.go index e16f2a52..df8d2d18 100644 --- a/hrp/tests/extract_test.go +++ b/hrp/tests/extract_test.go @@ -3,7 +3,7 @@ package tests import ( "testing" - "github.com/httprunner/httprunner/hrp" + "github.com/httprunner/httprunner/v4/hrp" ) // reference extracted variables for validation in the same step diff --git a/hrp/tests/function_test.go b/hrp/tests/function_test.go index 09dfa2f1..c6acc493 100644 --- a/hrp/tests/function_test.go +++ b/hrp/tests/function_test.go @@ -3,7 +3,7 @@ package tests import ( "testing" - "github.com/httprunner/httprunner/hrp" + "github.com/httprunner/httprunner/v4/hrp" ) func TestCaseCallFunction(t *testing.T) { diff --git a/hrp/tests/protocol_test.go b/hrp/tests/protocol_test.go index b093bb39..42e98207 100644 --- a/hrp/tests/protocol_test.go +++ b/hrp/tests/protocol_test.go @@ -3,7 +3,7 @@ package tests import ( "testing" - "github.com/httprunner/httprunner/hrp" + "github.com/httprunner/httprunner/v4/hrp" ) func TestHTTPProtocol(t *testing.T) { diff --git a/hrp/tests/rendezvous_test.go b/hrp/tests/rendezvous_test.go index ce68c2e5..59e03ba6 100644 --- a/hrp/tests/rendezvous_test.go +++ b/hrp/tests/rendezvous_test.go @@ -3,7 +3,7 @@ package tests import ( "testing" - "github.com/httprunner/httprunner/hrp" + "github.com/httprunner/httprunner/v4/hrp" ) func TestRendezvous(t *testing.T) { diff --git a/hrp/tests/request_test.go b/hrp/tests/request_test.go index 59f228df..bc0cc08e 100644 --- a/hrp/tests/request_test.go +++ b/hrp/tests/request_test.go @@ -3,7 +3,7 @@ package tests import ( "testing" - "github.com/httprunner/httprunner/hrp" + "github.com/httprunner/httprunner/v4/hrp" ) func TestCaseBasicRequest(t *testing.T) { diff --git a/hrp/tests/validate_test.go b/hrp/tests/validate_test.go index 94922a98..3b94b1d4 100644 --- a/hrp/tests/validate_test.go +++ b/hrp/tests/validate_test.go @@ -3,7 +3,7 @@ package tests import ( "testing" - "github.com/httprunner/httprunner/hrp" + "github.com/httprunner/httprunner/v4/hrp" ) func TestCaseValidateStep(t *testing.T) { diff --git a/hrp/tests/variables_test.go b/hrp/tests/variables_test.go index 1bc73d6a..b9659654 100644 --- a/hrp/tests/variables_test.go +++ b/hrp/tests/variables_test.go @@ -3,7 +3,7 @@ package tests import ( "testing" - "github.com/httprunner/httprunner/hrp" + "github.com/httprunner/httprunner/v4/hrp" ) func TestCaseConfigVariables(t *testing.T) { From 2c7f50aaad3563d98a3c4ed07f8612cd6c406cc7 Mon Sep 17 00:00:00 2001 From: buyuxiang <347586493@qq.com> Date: Mon, 9 May 2022 22:05:35 +0800 Subject: [PATCH 033/109] fix: improve RPS accuracy --- hrp/boomer.go | 7 +++++++ hrp/internal/boomer/boomer.go | 4 ++++ hrp/internal/boomer/output.go | 8 ++++---- hrp/internal/boomer/runner.go | 3 +-- hrp/internal/boomer/stats.go | 34 ++++++++-------------------------- 5 files changed, 24 insertions(+), 32 deletions(-) diff --git a/hrp/boomer.go b/hrp/boomer.go index c47db199..8d5e0266 100644 --- a/hrp/boomer.go +++ b/hrp/boomer.go @@ -99,6 +99,9 @@ func (b *HRPBoomer) convertBoomerTask(testcase *TestCase, rendezvousList []*Rend parametersIterator := caseRunner.parametersIterator parametersIterator.SetUnlimitedMode() + // reset start time only once + once := sync.Once{} + return &boomer.Task{ Name: testcase.Config.Name, Weight: testcase.Config.Weight, @@ -115,6 +118,10 @@ func (b *HRPBoomer) convertBoomerTask(testcase *TestCase, rendezvousList []*Rend startTime := time.Now() for _, step := range testcase.TestSteps { + // reset start time only once before step + once.Do(func() { + b.Boomer.ResetStartTime() + }) stepResult, err := step.Run(sessionRunner) if err != nil { // step failed diff --git a/hrp/internal/boomer/boomer.go b/hrp/internal/boomer/boomer.go index 83459a4f..6c67007d 100644 --- a/hrp/internal/boomer/boomer.go +++ b/hrp/internal/boomer/boomer.go @@ -170,3 +170,7 @@ func (b *Boomer) GetSpawnDoneChan() chan struct{} { func (b *Boomer) GetSpawnCount() int { return b.localRunner.spawnCount } + +func (b *Boomer) ResetStartTime() { + b.localRunner.stats.total.resetStartTime() +} diff --git a/hrp/internal/boomer/output.go b/hrp/internal/boomer/output.go index 79284cf7..eb34540a 100644 --- a/hrp/internal/boomer/output.go +++ b/hrp/internal/boomer/output.go @@ -10,6 +10,7 @@ import ( "github.com/google/uuid" "github.com/olekukonko/tablewriter" + "github.com/pkg/errors" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/push" "github.com/rs/zerolog/log" @@ -264,10 +265,9 @@ func deserializeStatsEntry(stat interface{}) (entryOutput *statsEntryOutput, err var duration float64 if entry.Name == "Total" { - duration = float64(entry.LastRequestTimestamp - entry.StartTime) - // fix: avoid divide by zero - if duration < 1 { - duration = 1 + duration = float64(entry.LastRequestTimestamp-entry.StartTime) / 1e3 + if duration == 0 { + return nil, errors.New("no step specified") } } else { duration = float64(reportStatsInterval / time.Second) diff --git a/hrp/internal/boomer/runner.go b/hrp/internal/boomer/runner.go index 25ad1bbf..68109901 100644 --- a/hrp/internal/boomer/runner.go +++ b/hrp/internal/boomer/runner.go @@ -11,7 +11,6 @@ import ( "time" "github.com/olekukonko/tablewriter" - "github.com/rs/zerolog/log" ) @@ -154,7 +153,7 @@ func (r *runner) reportTestResult() { if err != nil { return } - duration := time.Duration(entryTotalOutput.LastRequestTimestamp-entryTotalOutput.StartTime) * time.Second + duration := time.Duration(entryTotalOutput.LastRequestTimestamp-entryTotalOutput.StartTime) * time.Millisecond currentTime := time.Now() println(fmt.Sprint("=========================================== Statistics Summary ==========================================")) println(fmt.Sprintf("Current time: %s, Users: %v, Duration: %v, Accumulated Transactions: %d Passed, %d Failed", diff --git a/hrp/internal/boomer/stats.go b/hrp/internal/boomer/stats.go index b15c655d..8fdd3897 100644 --- a/hrp/internal/boomer/stats.go +++ b/hrp/internal/boomer/stats.go @@ -1,6 +1,7 @@ package boomer import ( + "sync/atomic" "time" "github.com/httprunner/httprunner/hrp/internal/json" @@ -101,8 +102,6 @@ func (s *requestStats) get(name string, method string) (entry *statsEntry) { newEntry := &statsEntry{ Name: name, Method: method, - NumReqsPerSec: make(map[int64]int64), - NumFailPerSec: make(map[int64]int64), ResponseTimes: make(map[int64]int64), } s.entries[name+method] = newEntry @@ -171,10 +170,6 @@ type statsEntry struct { MinResponseTime int64 `json:"min_response_time"` // Maximum response time MaxResponseTime int64 `json:"max_response_time"` - // A {second => request_count} dict that holds the number of requests made per second - NumReqsPerSec map[int64]int64 `json:"num_reqs_per_sec"` - // A (second => failure_count) dict that hold the number of failures per second - NumFailPerSec map[int64]int64 `json:"num_fail_per_sec"` // A {response_time => count} dict that holds the response time distribution of all the requests // The keys (the response time in ms) are rounded to store 1, 2, ... 9, 10, 20. .. 90, // 100, 200 .. 900, 1000, 2000 ... 9000, in order to save memory. @@ -191,17 +186,19 @@ type statsEntry struct { NumNoneRequests int64 `json:"num_none_requests"` } +func (s *statsEntry) resetStartTime() { + atomic.StoreInt64(&s.StartTime, time.Duration(time.Now().UnixNano()).Milliseconds()) +} + func (s *statsEntry) reset() { - s.StartTime = time.Now().Unix() + atomic.StoreInt64(&s.StartTime, time.Duration(time.Now().UnixNano()).Milliseconds()) s.NumRequests = 0 s.NumFailures = 0 s.TotalResponseTime = 0 s.ResponseTimes = make(map[int64]int64) s.MinResponseTime = 0 s.MaxResponseTime = 0 - s.LastRequestTimestamp = time.Now().Unix() - s.NumReqsPerSec = make(map[int64]int64) - s.NumFailPerSec = make(map[int64]int64) + s.LastRequestTimestamp = time.Duration(time.Now().UnixNano()).Milliseconds() s.TotalContentLength = 0 } @@ -215,15 +212,7 @@ func (s *statsEntry) log(responseTime int64, contentLength int64) { } func (s *statsEntry) logTimeOfRequest() { - key := time.Now().Unix() - _, ok := s.NumReqsPerSec[key] - if !ok { - s.NumReqsPerSec[key] = 1 - } else { - s.NumReqsPerSec[key]++ - } - - s.LastRequestTimestamp = key + s.LastRequestTimestamp = time.Duration(time.Now().UnixNano()).Milliseconds() } func (s *statsEntry) logResponseTime(responseTime int64) { @@ -267,13 +256,6 @@ func (s *statsEntry) logResponseTime(responseTime int64) { func (s *statsEntry) logFailures() { s.NumFailures++ - key := time.Now().Unix() - _, ok := s.NumFailPerSec[key] - if !ok { - s.NumFailPerSec[key] = 1 - } else { - s.NumFailPerSec[key]++ - } } func (s *statsEntry) serialize() map[string]interface{} { From b4e2c553501877afe94c0447c24dd40b5d2bdf97 Mon Sep 17 00:00:00 2001 From: xucong053 <xucong053@outlook.com> Date: Thu, 12 May 2022 11:26:50 +0800 Subject: [PATCH 034/109] fix: add boomer mode(standalone/master/worker) --- hrp/cmd/boom.go | 2 +- hrp/internal/boomer/boomer.go | 45 +++++++++++++++++++++++++++++++++++ hrp/internal/boomer/output.go | 6 +++-- 3 files changed, 50 insertions(+), 3 deletions(-) diff --git a/hrp/cmd/boom.go b/hrp/cmd/boom.go index 66d2dec9..22d8782d 100644 --- a/hrp/cmd/boom.go +++ b/hrp/cmd/boom.go @@ -37,7 +37,7 @@ var boomCmd = &cobra.Command{ hrpBoomer.AddOutput(boomer.NewConsoleOutput()) } if prometheusPushgatewayURL != "" { - hrpBoomer.AddOutput(boomer.NewPrometheusPusherOutput(prometheusPushgatewayURL, "hrp")) + hrpBoomer.AddOutput(boomer.NewPrometheusPusherOutput(prometheusPushgatewayURL, "hrp", hrpBoomer.GetMode())) } hrpBoomer.SetDisableKeepAlive(disableKeepalive) hrpBoomer.SetDisableCompression(disableCompression) diff --git a/hrp/internal/boomer/boomer.go b/hrp/internal/boomer/boomer.go index 6c67007d..cc424b12 100644 --- a/hrp/internal/boomer/boomer.go +++ b/hrp/internal/boomer/boomer.go @@ -7,11 +7,26 @@ import ( "syscall" "time" + "github.com/pkg/errors" "github.com/rs/zerolog/log" ) +// Mode is the running mode of boomer, both standalone and distributed are supported. +type Mode int + +const ( + // DistributedMasterMode requires being connected by each worker. + DistributedMasterMode Mode = iota + // DistributedWorkerMode requires connecting to a master. + DistributedWorkerMode + // StandaloneMode will run without a master. + StandaloneMode +) + // A Boomer is used to run tasks. type Boomer struct { + mode Mode + localRunner *localRunner cpuProfile string @@ -24,9 +39,39 @@ type Boomer struct { disableCompression bool } +// SetMode only accepts boomer.DistributedMasterMode、boomer.DistributedWorkerMode and boomer.StandaloneMode. +func (b *Boomer) SetMode(mode Mode) { + switch mode { + case DistributedMasterMode: + b.mode = DistributedMasterMode + case DistributedWorkerMode: + b.mode = DistributedWorkerMode + case StandaloneMode: + b.mode = StandaloneMode + default: + log.Error().Err(errors.New("Invalid mode, ignored!")) + } +} + +// GetMode returns boomer operating mode +func (b *Boomer) GetMode() string { + switch b.mode { + case DistributedMasterMode: + return "master" + case DistributedWorkerMode: + return "worker" + case StandaloneMode: + return "standalone" + default: + log.Error().Err(errors.New("Invalid mode, ignored!")) + return "" + } +} + // NewStandaloneBoomer returns a new Boomer, which can run without master. func NewStandaloneBoomer(spawnCount int, spawnRate float64) *Boomer { return &Boomer{ + mode: StandaloneMode, localRunner: newLocalRunner(spawnCount, spawnRate), } } diff --git a/hrp/internal/boomer/output.go b/hrp/internal/boomer/output.go index 3bcca9d9..db77b053 100644 --- a/hrp/internal/boomer/output.go +++ b/hrp/internal/boomer/output.go @@ -471,10 +471,12 @@ var ( ) // NewPrometheusPusherOutput returns a PrometheusPusherOutput. -func NewPrometheusPusherOutput(gatewayURL, jobName string) *PrometheusPusherOutput { +func NewPrometheusPusherOutput(gatewayURL, jobName string, mode string) *PrometheusPusherOutput { nodeUUID, _ := uuid.NewUUID() return &PrometheusPusherOutput{ - pusher: push.New(gatewayURL, jobName).Grouping("instance", nodeUUID.String()), + pusher: push.New(gatewayURL, jobName). + Grouping("instance", nodeUUID.String()). + Grouping("mode", mode), } } From 6aee6fba030791b05a1bbe0f755a4f464ff8ee67 Mon Sep 17 00:00:00 2001 From: debugtalk <mail@debugtalk.com> Date: Thu, 12 May 2022 11:40:13 +0800 Subject: [PATCH 035/109] change: update wechat qrcode --- README.en.md | 2 +- README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.en.md b/README.en.md index d1a564e0..555de35a 100644 --- a/README.en.md +++ b/README.en.md @@ -108,7 +108,7 @@ Use "hrp [command] --help" for more information about a command. 关注 HttpRunner 的微信公众号,第一时间获得最新资讯。 -<img src="docs/assets/qrcode.jpg" alt="HttpRunner" width="200"> +<img src="https://httprunner.com/image/qrcode.png" alt="HttpRunner" width="400"> 如果你期望加入 HttpRunner 核心用户群,请填写[用户调研问卷][survey]并留下你的联系方式,作者将拉你进群。 diff --git a/README.md b/README.md index 0581a634..f8949730 100644 --- a/README.md +++ b/README.md @@ -117,7 +117,7 @@ HttpRunner is in Sentry Sponsored plan. 关注 HttpRunner 的微信公众号,第一时间获得最新资讯。 -<img src="https://httprunner.com/image/qrcode.jpg" alt="HttpRunner" width="200"> +<img src="https://httprunner.com/image/qrcode.png" alt="HttpRunner" width="400"> 如果你期望加入 HttpRunner 核心用户群,请填写[用户调研问卷][survey]并留下你的联系方式,作者将拉你进群。 From 64a997b838b90e3259352973954dc8eaaa234735 Mon Sep 17 00:00:00 2001 From: debugtalk <mail@debugtalk.com> Date: Thu, 12 May 2022 13:40:20 +0800 Subject: [PATCH 036/109] change: update hrp flow image --- README.en.md | 2 +- docs/assets/hrp-flow.jpg | Bin 353941 -> 361376 bytes 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/README.en.md b/README.en.md index 555de35a..ce40fffe 100644 --- a/README.en.md +++ b/README.en.md @@ -12,7 +12,7 @@ > HttpRunner [用户调研问卷][survey] 持续收集中,我们将基于用户反馈动态调整产品特性和需求优先级。 -![flow chart](docs/assets/hrp-flow.jpg) +![flow chart](https://httprunner.com/image/hrp-flow.jpg) [CHANGELOG] | [中文] diff --git a/docs/assets/hrp-flow.jpg b/docs/assets/hrp-flow.jpg index 01a0d05086690e984ccdd2f50511fb5d808ab1db..aa480edeeb4e826ba75bb92b33234270c6c0dd87 100644 GIT binary patch delta 295344 zcmdpeXIN9))@~4#EsBT=NL48!p-BlHYy_kP2qg5P6oG`$LXjF9oq!NS6bwq}2}L@g zsi2@#DFNw%^b(|Zytwy1=WM@QzWe9?x$6PeB6H1=G2Zcx+18i~-^TGeg7eMyBa%lL z84eG?U&j#u^B;^%$Br@_XZiU7;CC9pz{q%nk@4tJHdYoEX24n8X-1%;!cAkinXf-S zt)x^KX7BBTD|+8E22#FdV(}=ERJJT4?|=?V&$_Jn_z5ASzCqOS!w<0bZRbp!C!??( zgdK2%f$<0v!yiZg_=AZVaD)-SaD<VG{ph7*XJl?bna>K!-t~HzaLzcbsFUL+%scUY z*BB?4ki3a~@eer=dilJt!mUT7ZX8^*r2gjs;3Ojh;0QY-JK!4NAouAX=Wy&dWN_E| z<Z)_!ivP{8Dy{^32FG_2fzeVOJ$13XB7z7yl{qwgy)?>3)_4%L_N9tcHEC4&0ro?# zE1@3-w#Dja%VrtXm!^DgGMH&>Dd_n@&6bBJ-Am6!RPbzV&CT)DLfg|b4SJ6@1zo^z zJU~V9WKRpg>_+J4F&%a-?P@u1Ylp7>1f;%wfYRY9`B%~ZqU`^s+JBWO^B;--wdgjh z#cDiIdddIfwduld|G4BeXEY7Qd~c4~9d2OTp5IdY&*ib61d4{&BzT`b7O}U!SM!gO z?vu1@N-Ly;P0xmdz=MBWGL&5eIse1V$N#u~fV$mg@}IB%{+~<CH(q0{-0dbj*JgG2 zh1I^5{}lZy$@@>q>au&P$$)fv&ul0!UEg5q3dO^yc`2%EM{M)D!pnO(gGz(7s9}1% z-ZG}RA|xZRcJ}DyjJRBajjD;1XXIR^$u8068O$xR5)2PYj=~{a$*vSv5^r95PF!HX z*M9pX(exKWd@qp66Eiu#4u)oY5z`&q@aS<2+xhCg(w54#K``exi+jCr{TB-*H;fZ| z$^@NNyR=HgcoQNECId-iL9!r8EYI5AymGv6XS}7X`ly=UFneg4S?_SpJxiTC_ySEl z-dC4A^S!>v@+*{Y&EhQEB+Ov+L@0z<_Z7Kc;SKW3shrD4dH8!6${TAWM#njWpv{;@ zP{5?M@*+H8r~o|ABi7&f#U}<Oz7n1pSTiv`tODzkFR8Nis=1Q4ZeuyKS`;_egPwfi zXJfVBIB}t4_{V^USlpO<FS&dM(%~C%qpr@-b1ua%t3t?SByy@kKGyPO39#KcTZMwg zgPVT>u0KG?OZpW&w1m%eW*b8sb}_fbR+e5isb6s0;sSQTO?i<+uJOl*#Jo&OQ}S0a zwJGZYZz?{ca?_JWDyp&XP0c9E!r)w_)iW*8%-Avb*<Sa^W0t*T6k_1?tiLYMT(2`a zx_-8J6K9Z|LAA%IS76-ta+1+LAoqd+R&Rp3vYkJLQaX6IstYEfem2t|<HY4)`vGa4 zuris~EI7$Sb~Ip9oQ}1@R95g;kBhpdb+4IHOy-l8)Kf{1EMJ__l1V6nx16L-d6OF> zBV+COc%Nvk%*Tv{+*v4ab3uFOG-ljw0bz>=LUEf(>9>-sn+=CFy{v@_k{)G=+vvmA z<HHuSlU<U`zvC@y3efDBHZwsf4$C<GZ2jzjy>iO1fT|Lpr|WYS3g_jwGiJSQXQ8%~ zlNmILkxSxv>G#ZA86FxT6a<3%i}U}8mEs>ueyKMtX4(4$sm)h9v29~Og?hN<Nv4dd zCju!J(1ygprgsN939;pjk*sy&+BdWdH&y~aX1cZLSU@ZcX0oKx$y#OYVfzc->9({M zzNxX#eeXVNh10ugu>n@r?;E}DyDoG~HR|e#r=9-PBuqf9GCjckJdtxu^LpZ?rxyt~ z>X<Zer`e=&4>nmajDU9x(~7*dv%~pc{P!Dac&SGn*^;Z)NP(V~B&C6ZNyL5XSFJKN zIFwBv@%CBNO5cx7_b;F2W3zjA`V40WN*(azEs;Fr5DhjLS)1q&@Y&Ms-|ZbUtB8%e zxcJnR&RG;yX6ZMohIlipDwhyElKLWUOSVo?X!J7sL8{6n;HQGYvn#28h~0@xO6CNg zl(_vJi`6Q_YSs%1`U{Fa{6v^LU=qVz5Q5F3l=IgX8*~u6BK#w5z7Kn7rJw}iryV?- zWo>qKsaJBMiAFTY<HYrP!s!$BtQ*S~UW1EPw@g9?cAERdCrRdTI}2wE4!CUsC`ikh zT02h|Q`n=qtEcM;r?64a1;l;N+VuN)M1S;2DI6&bznWb%6ARwBc&sOu5kN?1VG>I^ z#0^tF1YHKFDOrD!qNxyPt?n0Se{?MVD5!vZG82_r8zAUzS(sqL3f9y#Q_i<tIuSH% z^FgY`d{bpQgCf7~gtlP?t94TDCY%3=OMYQZE_~RM3b)OTP!w-zts)w}*I4&r`1o<x ztMZMdPN_|tqZM3)!%iL4NaQe0O*{wI{)jnowhM3FOKpc6kk^ASuh(EOzd0qzjh)-B zuCDfOS8tqj`81BriR=aCcIImy9y5^<&IJQA$p$G}&Aqy1Jw+;bA?cf9`MFIc^Di`V z_tN8a;i8ISD1nyE*gM>f+<^1P0LK^}9N`8q=K7Zm_#Q1Fe-0@(9zxMNRalaBF*#Ne za#|A&&o`7g_@Yx;BJM}UzO;O1%fp=VMKy%9QLP7^A-?VIDc9p?R_X5S7F?0BT@NBi z!QpT;{_s!WD?i=2E0jwWEf$PG^7-vc54!|)gwE${Q}dT#9;&jr!>vP`X`e9aYNQ~& z8moXx&QN)U$ME4P97N%K!8rpAzN@^qaYsE5N*;5~=#c3A&W(OaMGk-dGQ(qipJ2Qr z($<th)L&_~2&P@QT|uP2@+a;pbRzQhT9rw~gdxn$#=8exN-^Dh5ESIt+hRXh#g%2f zpmL#Y;tk6lgL}SndH&imBa*OXZNYnth9OQ~Swrnzb82N52V6Ih`|GSrG%g_#`AmT) z*M-g{5{8a2i4vYNFmVx1F*BSd@cd+A!~IAcAuOLc>WF*F!zOsxm)$xbs5<ZE&0^+9 zP<P3pMn3_W?fpLi;wpAKn-5TT2$$L!#RWlD%@@|FgV!dV^?m|uSCOoyD-rurejVEu zT|WVRHH@y|1R2(&d<100k>i+fhqz1rk8t+o$(xlWT?)lnQL*9S_<pSpxKKhuSu+uu zLup+SFfCB|M#{Y1^*C3tc!-p5Zj=qg-Pdt?aoTy6*8uVM%CTh^O5N5h(aY>;;>g?! zQf5{n(0w?;sY@Z1$+^?ARosLKhg;@EPdj$JR9%kAk>)*-?_0M#=!mgu9KFR?Vld#L zXq9qVTSUcsCEZFW6VXWS(OO50$s(^H)+jn+T2P2Wij_Q8y}VkhrpM{{4fz3YEBOk{ zy{TdLy}mEWlsLMiNX>l6(L(NQw^ELfXGRTXQ|EGlApWUkZ<&q+XaR!!iJY3~6ns|X zhyTZWvo?yx6%p9IKukwy6wRm;-gcJTxS8x@rUwBw&8XMJCbR{HPIG%JS3+sC@R#;u z!kNV=w@+z>(0pTd&u0#bk)QKZ4nnZs81`tG(=|4TI^p&5j>WhV-Q3LD5tQF2@r&7R zraSi$jvsQ?oxzS?6sS$x=upA?A367*eiw4w^!{F2mAX-tj)9sgCsiSRgTCj)bO4h- zW+rp9^VF`$K*>2YZJ;u{iDc>efXr~cdAl68WUQxjEAffH&vZ+w^>?=|-)p&=7#UFN zj@OI&jhX(85!O3|Dr5||6^L10&KTZ}rZ_q%pC4(~8VhAnJkeZ-Gdq#A_j+)>s@gE% z%kpAUDY@7LGn|8%&UL%`;eckjMdb(>pig*j2DZFPas|SHy0Ql6n?w9(hpM+jqtlJ^ z#)M}}lU<!diEYDb$M)zc<tDC6gQ<f&ww#{^`~wnuw%;NLdbHW*UlvX_xwic<Q)-HX za123^lPo2!^5Z=7p}EDbbNIO2q&aZ5=@%2-+<pnnvjRk+XvT+@?bH`*%RQ~|VAx!3 z(>FVq`+8DZ62#$dPIkFzi{4pN1(=A9tVBY&rnh$;cXFlmnDvCg!qatgSnRiFX(C)A z_(6W-{npHRpSi|N5cf@8DLrwr9@%1as1;kWXC_rXR&BJ*EcImdny1>TLekK#!QdOD z**YEbSvi3E*0ZuHYDURsT;!#$OH5M9y#yOox-V^`L&5SVKy`XDTuga;S7~{NE)%w; z++q8i`TAi`G&CyOJC3kH2PMv<7u=s6cy6U*gA|H2OvkVYpXROmshCHQt_rue%qdu^ z8PF-$=@nFW#<df!8W<&(D-;fb(Y0@H2x1B!IR&4PFPsY)uwGr`<4gVQvs7XDSY~F< z7miR{UE^02a}w#U>u;jUVLlxoXNZudrPCWH8kb%21Cw^0%1QUS2WKZx;y{7MbRvhm zTAU6vCnvJFn!Gjz2R8<P(~b_&d&?Qqo;ajDJKZx7kN}e{RvJ9hXIu*>PiysP$)|r^ zLnvEDMkmT;LE+x{>z2NATCc?870Cnr-`dK0XNKWe(c{{$)bjH)<5}AZ0q;<(p9s)i zMiGqAcXN<?63x}J%E8!5YGexDa>Hs0?$t3xlc8D(%X$M>)C<61#F)u3h4Jr!Tz9rY zX(>@UYF@)>nXBR|%IpD@Y1?)VO}ns91r{7?R=im66{7{b7Ji_mZfi~?$*S=iOkB(c zNUkkCeS2*hZcCoMx5!6rxBNuoA`{gMF>Li&5ACcgb3dgkb!Qc&2B=R|<y~nCUK-|f zf|JR~w!Iz!fVev~nzDdj;}QV}&k8e?UvfVrU})Iav1<*4+s;=9_ZB_tY*7SOClmyd z#Hp5)cw@26yv-on*QK3ydHyVcyS~lID$Oe70QO}=Caku$5Fe4R4+Mpp)M7XQst?W| zc5wmNASw?dQUcXeosfs(LG<i|ca@jVnl|4zbr!<sX{$q(@jR278&{Y!l-&IWL*@p} zoU1!;SlqH<dPakF7hT}%R44ZpE!xH*%HSMaBoYy#oU@=(_wiQlTG@EwyeqYSAX#NV z$JiVp=>I-QfXEj!e{1dxCMBkM?j*d3Pkxb$b%TN}LHevcGpKIGnZaQ&mB8pYGi3*d zRQi;RKt2+BHXGi8x2E3Ss#-ENnUUlO?Ope)nt#T5!WZ0(&^-oPKdXHbv8kXyZ3BC! zoBcr!2ad@_V^u%;#nd2LK;c&BHjtFX)xqpsoe4K<Y_OboLcW|Kgk$)ulBu$6WiwrT za4xxwXP_+<kr1Pc_K=o#nU+?6Q!Pn&c+)eB2C8_|_8rhBV@^1Ij!75yBZw1sm8XNi zaPFux!GoLW4DPfTGv;u@nD!H(M=Uuo^?AF#r0|^f>I$Z7+U+L*Pn6yddZ+L_{QBu* zfIse>XJUB3@Sr=NfATcMsxeIa54&9E<PEMMB4sLK>hUWLXNcNxcJg*{uto!cztF&K z(kpDp==Qs-M~*SP5;YmYL1roCBufvaO{ws}k)~I;(uMEn?EuW1$_^>Hu%;Zwc|Z@H zxpIAT%BBoexGuIGznzqwku$yNKg_1QI4tB9uNZ?Plq{{4ygKPSc>37&i-$jdgx|H5 z1c{zev2x3mQj?rzVb!6G-hadst=ro;GkCp7zxa0Kw!MCO*SRf=H9~eY!;3~Ai7xAV zrA96OVMS&d$)Xsz*2t88zJZScrIY^^hw3+gbJPgx(_`oIbW?XcBy;n2X&IR`vvXNL zK_yMlzB)l5CuivU+Y`$dM8k&{GRLKz<a<{-UANsl=M-Sn(lb56^oSyI($Ut)-t^)( zlxo~t0Qc1g*H6+s{)uW^*1rY|$Nw5F{9w%`+&Fhs6mzdA09;BNH2OiaGeLyA8n^gn zP~oz|%^R%K*yh~v-n@M8odhZ!tl%=1m!45+47p*a#|i=IyGOg+v)52QKhcmpRR{)y zVKCchb1JF8r5Sg>`F(tVd%@!LjT-c2EYFyB3^sL8fa7lQ9Pip`gro@ndn*@=+~vwT zoyk<pxiBaP7tsmHfkEVuCp7g7-G2gVEW2arXE#mt+y^x3TU4NRSo=yzyDGLv_h3C* zJqs^r35_2$^~rhIWdo}H*u+o3nEUtpzQY5jvWUgEXNA+Vt9qIYCcP&;dqT}qvx+RE zh9V=KZ6q`0M<Tn|0?l+RTv4Ve!6A5%h#W@Qym4y}LBYeV$-j;Qh|LM{{u@Sx@~=tX z!$#elCQ<Qsv&y^IkDHkG$wg<8zDLqWzCtEZVPiv`p$zwv2lyFE;X5Zhec=Mc!V2r@ z{3zP(2`AW@vX~?)yZ0K}I(lT!o9tYmCh*a$I#lg7UX_q>WscWAtJ2hFf@p>Ll$YV8 z`Z|gZrdtRU?Wi9l?>rl<MZW7`4V>E6AXU0(8=2Bt24VT-<p`Hd@6AHTK5s?!`A@x* zaI;!($J&Da{L=my`6S8mu4lc0uA>7m^@89MgQcU1791U+vXx-Jv*J%~(j-;rwg|kl zfUbu)cT3aFkNlT+LhprrNT0&Y%J<3<;$Fzhg-dUJD4Sj`&px1Cte~Bp#?RV)C#oJq zLi)S;loxujL%RE4y8~5Q*WvAm+&Rkr7c#Ci<Q8pJ+Hu=d{&1Q+#uJ%(o_{k!J-KI) zpi{$D%R8-76^;mul+u`i%O&2H>6dowHx`+kG}r)2sZ2-$i(ltDIb+(<{<AA{x}8!S z#p(E0${nNGErq+EGi~99{RNK>#PbfyBDkLI%Xkf}ty|3f1n@BW`I|+A5Jv_EPQ*>S z`~>(RB)v3vw0G0skp7~%=!p-qMO?tBQJh{4nO*xBX)s67LtLoBtSI;8*qPhVlOd+P zKCae%IP+JRHQ1<8`D_-jG%fl##NPe<a(`xBBhKxjH0JJbc~_sFG|t0Ex{oE`Zun0? zU}kE}t15Fr3MtCbqsY-M|0f^^m9cl?M^jLZ9ISV-OCj)?+p6(<J1DDo1`G^ec)q5U zg+UA{es#(>EZaF@J|k9Mx1W_e(be6+s+#|d9wGsW6D?aVqOcUTYEfgYK_=Dw{kOzs zH_guCF?tbvD@u9JRdcQVuKpFiB9?Q|4xuMrsk8Khkhe=2yR1I}ob++bGUh{M6E7r_ zzC-yuT_wMv>pF*!+$mO#Neh#mZaC><fQPF?;)(MMclTM|EC_VFV#_NRIpTG;%M+{p zMglxm=Lj3MWI>%Zv)jd4(A=SkMwL7C#qc)pVtJc6aIJ1|HfsRoOEbvJ$@Yb<iwU<o zm>0l_Oa7)sxlgQp=yPeVAd%19Wns)*OCi1QU3{IUzAH-r-PyS3cJtpd;)X6sGeB@B z1tkt+J@ebOa7n}9#C>Q5JuN9f)#7!9nIXhreO*}JU&3_(%M*V0i>fcluiD^7#@ex$ z8N~y+eJ|czk0x*4uU+z{OazX}P6Mf)L_I$hh*e+_9?Yj*)o$03<^lOKZ)N&~+<5#} zRfpV<`C@gzJ2{fd)6<`h9F7G}G6Ei41zaUaTwvnFoSYq``wf<!B9~c<QSLexU!N75 zbi?Whc^56f5oDp{DGFS*6ABGX8v=Z+x#2lq5v=F(RbK1+Zr%V;x3nNDY1s0O-pQFJ z8G*>0DRpR}x2P#o6<n!(c&)InU)3`(8lqg(gJP7q4SCeFS2d?(fnDCpFTZ7z%^ z=T2hsf_?%@25@MpAZ4e%b9kQS_Cl0Xb`d_YF%O}<WhQfeP1D=Sa&k%bQb~1w&X<bp zyFSxA$nIFDXSLGpIvb(l_n;idhSGatk2V&Jd2DpKwcVRu;HZ0#&g+h|tfn393b%~V zao5^E;}WE75drR`gWE0IYdLb_I<!>APXuD*ZQr2;UJtYSO?`GR>lSxNZ$|g^T<eU+ zN7m94nxlrF7&uFlXeQ>S81=#tbL4!bceg)8=5EpF8hBp-YSSvX6sPfs#pTUJuEQBk z?~PnxeI&=Wwu1W_0_`mMW28>C923T>TvuWtFR7LJkh9RdU;RU_KeZH131XJxP{vF} zRE4gAabn?()2iDy$`$7uyXk(><FAZRd}n7~fKXrvKhO2t8{*%B9Fthwot-$(y@)wO zg)4tID3}No-B^kg0B%HhY*|r8Rgz;uGrD(@dlSs_SIFYhXt*Z59p7eK37cMonn*eb zH#=1Vx*@2^KpMxcVue^~^1V(QOf}aWp=(auZB4EN0ue?el)cgX%(JPIw&d_#Gl&Z1 z``Gd(d(FeehPtv?7M>nJbcQ%XZ}-!eE${fiN>|n4!!hg~frUcw^955dbTaulSo7LL zKBYMXoo6!!Ss6LRUmju*SJ?;au^N@votX+(R7nk(^`^9msJ?3L@n6IwkK~Z!4C%*K zmLC`Kp3U&7tM0QT`J0!esOm|g%Hwn!;oAM>nt>87m58E67<}oI@Ry$e$)n!`9U&lx z%2_LGD+>-s4$k=&Vdqqczqzx{9dwYBw^qVBcp*V<*OxBqH?sV&I#||I(rJkQCt$Ez zjpOrCa#MxE97|SqH791W;=^2EGC|fg33U7G=36nIohu_+l&4#^=2);>ueCJFuc$~z zxV(Dgbyj6(^A;_7#<}7Ur+nLdI5NSrHmKFGNxiv~`jX+tPXM|6Cjbf^T^s-=_TTG) zl=sM{NtPvArXgf=&y(NO7GNzRzMl{j4SaoZhgK6DaC^-l3{$$ynz!*-?;5JmJ#1~I zY9R07-Gr|WxWr7vB^}ww&mv&NBsp>+%MW*7jTGpi<>{MMnbH~PT5%$&l$cZv&nosv z>l!*+w)!LoeRWz^<PmJmvvB9ad+k-lo0*-#GVmXZ_I2}yS5CdnBjs0oc@Iw$D=|}- zXuZ8^I3-QwNx;-^>_~YF$?%ACgz!xgq1yFY4_1O)Syc;M741x1)Gf%!0v^9j9oGy} z<rlM}LVI<Gq|i18Zv`I&kSZ|Z+u+#;@V*v1DprU4V(A}wf<81WPJh(neRIymC1*UR z)f;5pey`hLc5Div%CfgYWfzF7i#`Z2+(h43bco2W#n6)S>$1uh6*3+T`sGvd(8iOu za=6+DN`W*yU!I!_)&Fi7Y|Hfr#ErHaz;#OHOn>d3yw_Wo%J<$O^0Rj<hE4l(E?Pjj zN^;V|UD;cBfC{ick`EmQyLC^geAvdTPD7ViF*IEN_$R<{s7zfIndNx=EJBsp?A<_| zrl!^g28)fi7F3kBg=xY7{e_n$S3~J9H7sZRowY3`b}WaH6=0J1l}v{$s*$+xbfS;j zin#Uj7{1o15Ro;r29Lu@Tk!la2c;4=IH*9N`B5?(49u_06s$eNyCnt%?-gjq-R}%Y z_g+!`V?v(<%rg4=g;4@(c=lVW!e>6Lsd_v{zpTwRIyz<~VpaU%j!L^g<<=1W0=J>j zAc%&C8q2W5dA{;b!(`QiuTdTGDqFQ|v(&BT`+|u&>EQ}GvZPNNW$d;&&%yBJdO=Z> z^v?T_S0Kd6*xA+kaTcX^8R1p#wSdV{RLOV@TnybTYv|(*xT9P@b2uH)Vt8=+>Pf;U zK?WsUo#1KQyGKU}U-+4X33tQ~m*08?8G!%$kJ$)U+K${Lu=XC&z|gM!Hlof08UAzm z?FVZfpj}MJt*B0P8oy{-z;z;Q;{k@<w_sxV!ldAM@^pE%Q$9JL$M!g|w_mGlrE72| z|LQeDJDHc$)It~@|9-wr+%h^=KRa%`Ti+|7-nY+S@p8>b%YJj?Cxp9YU>#;Kfb3>0 z3&)l;5yTzA4kY^x1i5#0RdKz^dB*c`mEQq_Qe#h2?NqsG_m^%Hvsu-=FYgChbZMz# z_PI!QIU&B%<-+ul&&oI@P1~hsqcPa3ah#`o=HZ%jL5blk3(xbeX%qb&UrJ7HFj7?5 zLo-k&KU^N{4T2v&&(Z!P8uLwIH7@mPS?~KF4W&EU?n`ia%LY5w0SmIUvUZ1#Hixi! z$@6I^W7iAfP~o{8YyuQIQ~>cS*;cG9>lsvLW|tV@38St1J*n-oa4N{Nihb5@ulu!^ zk|IfLolE%6*Nim-dFO7Z<?^CJbNb^1Hr?amEb`AK8z(i`$-&{Jm{pBr?$^!t(IkTx z>qYEAU;6XUPHK;3^))~$phLblKjuqXh5~hbDqggfof%4h$lYHwL`?oLT_Gl_z#h|g z3hct%uwvx(xWK=v6VqSYQ_~SxH>-QQfU`RP<|+&}xSc54q`Es1?BR1ip&y-SsN+3U zutZOKR)fqhrCEv_8q6u@ap|om7Y}OthFK>Xb;wIOh<XSmFBG+FU*?PHCZ+8at%xoX zH?q;7-XC>IrWUA;YcD=c+r}mr1bid5deHH)*@!|)nR7nd{2ON68woiE1a3k?-uCO; z!)_sd03%M^w4w9Uc}2np^FT-7eK4VFC1Zq2PP*A$4$ne-)XV$Rm2=w$rL?4&m|cOz z(OGvcNbQg~DtVd1sZP`IT3YlKQjU|{@JzYE=U8v&>T(A{`U4G|pQJ1U3YRM>$54nH zmXyNWTXZd~4DOdYBF`^rcha&1h6*3IK_b##_n2jw^jS^A%}jcPmo;lB;=DrRXgKBb zy!Mr2<i*|(G<t7uQ`LYF7RZ7f#U*Raq=dEg?mmlmYwn3t8Hmo3NEA0I)t4gxqgshm zOJ7hIc!Db4vvznWc39Y!au#<f21s4C9md!WB&X1lfMQ&2(}_vx(%yC2AnWzD<VNv? z7J^j#{BXp#)ZJp)rBmHrA{xXViHfSCofA+YairQJI~vSaBrCcn4KB`eM`Urp+2JN` zYIw^f?=riSDJ_{GN#BqL$Shf-!BEFU@YB;eOO>#k%qTNpti3Gp(}$TD*)f$I%(1cn zDL*}1_qwH?IrR%w>pM4NZdprI#C+tC%hDQ|O@U44OAueif)N>p9$9gQBx!@ap8#~n zce{iRe}!pxe^E=<S}$_W!Y1ooj;v9K=<FA28<Yxs)CrEWd~>tun&3wC!nNXW7h37k zn^ti52wtCHafHT2<c5QXWbY>+Mwn_0%jX#ePG;*Q6uu%xMW8cK+SLjCytQ2FMjJfR z5up-F&LfM=zF%u;J-gMVRge5k5P_<TQ>j{3Vw|R+?xum($aO9CE>O>+Hn2X=8>lR| zw%NX2uYE?%#74<_TpGFmp(ikK=(9v%6>097RDpKWxxDOfEegEfZABTAha<l1{QN|l zr@K1H4)RW56Gi$__(77s3e8<-g5h_7L9uYQL`5B5sHme_QEehR549+yEjZTZP4WIB z7jdpSw{}R&D@f=4u|_(p-{X@0S)l?NVSu?x1>dSq+$+Sk1vY-242$Bm?g>s*7O2UI zPDR^!u6}f8;^_vvn_}L%YfZUt5Gj=+m$SP~Zm9?LUVAc~CxK2d-^hqe^e*eH<(^f2 z#Vi>QHQ#bysYD_R=1sxQC3Ks|#l<$12`!UVgJ<!GyttXhjZ*Y`d?SA|k=@&ki^Dp= zd_&_WfV8oR$m=CG^$s+DYc^k$ukb+_e^^Hf1;<4d166bcFg*@%m2}&><4-{iv9$po zXxh)%ASh<G>Uh5rQ$($pp|+upNd3LR*(4a*VxIpPJWANJXlZ%^-JQUZ+lo~gvP9Lb z{jhp(ZvtJG$y+~_S?3NCvEJMX9H<U~d4BdF7G&z2A?fg6kiW9tlEC3vL%lVE94bt5 zM!{!Zj>?!|rUgz+DatJd+&jqRl^(2R-cb3%CNwECJUlrV!+fewGPjQ>y5GObPz`tE zhF9`s8&QWVoLA<|o61BHwRA9YC8m91*`%<b^2B&uXNB)z2c*$}S~#4{!exudUp9rX zo-8hl5`Jb>TOtTLdkSfmAmqnGOyQ}EZ(7Mjn7UMBoKL7U-sZe#PI01E$?LCXuvXm8 zZ>k_1n?xR5EoX$dIW-03Y)TtiFM5<|WQ`gg^)bxJCw7uQ>V8GHCsLcSrpoLVeXsC5 z3k=yj@t$QE-VuWn<+G!ToV!`;$A-YZSv{VX7rq+LiQUb@&NN4Uohq{rHvTZJ$d|Jo z%6|3_j1=o)a$iI$z?b=4IseBVv$<%ZBs!6niOdKtPf^!YJpNs0EaKH4FWVM{>lKwT zw%N^Nx7?Qo%s$_!*_<cxYhpk>{w=lV+_9f1+WdT;8PDyG`kbjwp;|`P4(q4XYwfwT z6|C4>`I;=As~X<98UBqhET;!&>B*Gu3+NX`VR)%j^8~{IX*th4@cILL10KI)+aEWE z6n!a5`@Nm?u%`CQiG4Tx(VT6vw>OFsO)~Nm&gb+}Rpp~zQo<+M&kctj=ZaQQ&w2$} zzUvZfnflo?M5Jy~1O?Ks5D%1z;Y+MyrgAL$tHL`Ph9YU+;?$Q_6t`~`52QrO8y|=G zVFt~5P%zpXg(@;o^45vhCB6Q2V&gexetNvS3Gq=^v8H1jY+e(oL;G#LmT2^vMsXiU z)u5qE(84(te<-|pn`p+n>yr>099?MMS#(3la#|W__+%zB^4KoVt4V>vw6aCp;UT*3 zQ)6hCd)A9o5-H$}Qd$)5_Ov?Pk3C=02n>f?3tlR}mmP3ha*Yc`9&Ca4Mzb^nhr7FB z>+-9OiC}rdWUbnG#2OmTaX29cUO8sB+4ZAoLkktryo;ID8`W#L(9d0dL45J>v82iY z&Kktc=U2N{BeS27a(AU}bjN6S_P)cekV3;`$lJASJKRHgZWuHAw`y#4gz#&{a&8)C zgFgYfzWXzK;vv|>+PaU|ZfvIi$lp*vMSKRbbprg=|IdOk%*fvabNwdR=bwUa{wDZ0 zjg2?EJr50B#QZYQ;8pE~Fpr`VHw_)5(8$_<sCJ#dBSi5e#?ifRudw3anZS<wdCbA5 z;lgb@kA1QCOWI>r3vPpKiLeVKwttFXELqALi(kMF9IkP1`~-Bxk3tR+<SjdVp!+9; zp=+TWb&YLrwnc92Tng&kGTr4>Xqdn=&pc4xUmH4vwc73%tiLPC7I=+i)Zre^SpF>L zx7(n*vz3SF`@Q`WeZ!IX`iigp<rjo=u4zjDRp)DP;_Lm>%-8Bt4t=1AJowWGwui?2 zuWk9g7V>xHnGc2F3glUoJ^!ir<9}iSSoo(#nZIN4Qs5VV1knHOEx#!CA1sW5{%IsL zE&;@iIW_&8L2UnYRnu;6Io}Dl7Wtm!&4|T{bcj`GR<~bWi)I&OpwF=FaIE;W5GW1Y z(d%UM_)GUgXZ>;{fPVN*_Wy#j$)GUG;2e@xP%yffm22w6Q#?mOZih;=d<k8{87OjL z9$T<F=k_16>BFnTDe_-_vp@Xz<G;uii>XSv`>w9F1fo-TKXfoqo>no3QbaEJfQ5(i zeYF+uHF@8xiL9Z_N^7e3{Y4_=!7m;%rW^v#^>5Z>NgtYL_cs%tkNr*UbC_R2!}VLe z65Os5KSn9!FCN?d3bE^d-~Q*_zd~$CCgg9D-TBSjziIrJ1_FQ4wD1>AFa8O(^uO4n z{|oH@Nt-{#GyX|Hmi|Mw|Dqs6KaN*{7sG$;FDmx!{i5REQTmr|{IC8g>zC92rJF<c zZ?5^9mw$;kY+rtZ=NB*kqn_vPUs3h;S5!S*(#HIX)PH&S7d`*&;f8-YEAH=>;4l0C z4%5Hw_xN4Qz5i$#@mI+GZLsZH+^^vNcdcuOIQe1zY(Spd?u4GN`?UCH`ytb_nc?}X z-TM<YwvmKiVf!!8+Lp4%Viw~5Za})Z>Rufhr;7YDa`ioTyOe%48GjlV^Lxdwo83P? zZfK+aY}g+ETZ-DRcBq?A<Da?({X@zFRgHhD7Bstc2vGT-ElLv`PEv)Nk8kAvd;{h} zn}O11c={cs@T}1hw#p~n>+;ES8hubpH?9FataO!;kmW4U_55a$m3(k?yy+^s9Ih=$ zJ^8r-lO*Q>!X(eu36^fQ`qK7_cBv>wJSWv=KJl6^BDnBiy>_kuasFBy<aJDNWpviq z?&AUhiPPY;@r8BPCIj<amy1@hGaP*qz94<15b_fCd}Lf;pgaC5xknDSsLG3JE!mOn zSXL}CWYYm?7&0jaX^jQw_(xb6S-ZN!Y&~+2D12#`f{XLxVHFqqB*k?wGA$I^W(|}! zh{hP|gG*nEEO0g=RSW6{_E`oc=a!jza4j6DTWL=6#l>bf&atdVE1*B}kU)|7S|mY| zkL)aMIh^WIIMQ8N2GKe<i)rZ6@!*JvRV)wWM9o$~5w?=CjjE#!;cd(OsB()6g05lC z6^Ye4a@-3kTB%pDa~V$|+BFL+Fo6IHzE#Va_Wu5&|8vv1MD3IANoHlS_Tn$6rPy<b zbChopS8Qc8NMqL`YRyVEuwidenH*YA-qb0h%Cl&&k5gT8DQ6V?Ft>5&LRV2a{9ea2 zMz&CPx@oB96`#<Ed<fyXjY55Sw!v)w92sRg3xbc`%}C0$EK!b{wo%4ot^Ea8fp@JJ zrdzZ~-8*54pYPNS(jj3`VPAfe%UZxqAu5O0OX+~M>oS~mW49t|I|hlWrB#C{m-)4< z#D&C`8~ip9+0G1%mjpzs<zy`0B*AmStWb8$2Cr!{Hg}3y$(Cm2o3-%@iYlcInB9gz z)e|4t+&~|E_nCj8?4%pl*#@U0U*>x}y7XMqvRm8E9nYzHIj_W8N<MNuCq1(;+G0$L z$}<_nGl}+`)U?h9!)`4+aLD&<=xwLl_8GY~R&>hu*<8XP!;@Kfjyu2L*L}9^XH=z} zyoN7C%8rDFI)SapljtAiO;2hc3`o|!Mcs=Z-0|(IO7_jIwvsYw@eI$MYb@!~jkuO| zzE#i%jF85d$N(5&Il)PLXTKmg=Z>NDSB1UH?->~e<tHW-YCFk(BuKdjeVsHV`@mq$ z+`Cqbr}{DO8V_+u->j%I>(Y#;O$LJ|X}8D160(FA5;fpa=8-Q8?8~g;q9cj{$1*JU z@M;{EODC(PhOCjie7WLR<_gO^vcceHkkx=7wXO+B+LVGk>P?X@O9?A)A7Hg2NtPum zyN+c#rNx?GdUU0zU)UzG%zk3h16TyLii@ZO9K(oxwdE?e3J<hu6KHpJ&Fi$A2)A5I z`VNH;R0#)ahrOUr*55u@a8^e@mUwW7owA2oCR)@>sHm5!2A#?#m`XOBN}4LgX6o80 z?vpjwF^Vq(2jdL+*801*Ura{8Y7xZbt~X=_|IGdXTo-I{g5(f#Zhn+!597J8_sZ2Y zS)`BMs(-X06*ui|fy@x@oz136#V^Rvvw=P@)BCnxb>BJ3eBP08_X5)$g69m=F&yn$ z6Jz8<VM3_<Q5nJoa|R&6^9SQ;9Lo)M%)g#$|D6Koiv2GsaEQajKg`9-#PM`DX}+7i zi<`VJd95yoH$Xv*OA66d&!twNW$l)(<3WxQ<zWXg37q;uwhO*3W;PY0MvD<=b|0qs zp&#wX6n6JodAAvYb%d>HeJlfn#>5_&vM_!KgLRY<RZCR1?4`mjqn;eBA{Ohus6N!= z=dAg%!K!1(8KmUC@d<gpb#tt^?9&S+`OyBw#*yONYo|tMYvn)V&M0&s9z_YHX5M~2 zw0V))lC`vJz1r?pnMzgLjnLv-F4?7UW7F%2@1cB95Yc?$V3I=(>X}PpbtilVzgHtT zVyKsgZ7i6vjJ~WMci#FsE^4f{y}!t&nkIiLW{_X}7Rm1?0AghB=J$111p&O0kWi!s z54N7PEkNU~Cl50#;h$$7TxAU%K44Tb8&$Vbs2-Ah6HWRFFl(8H_n5IHi_(CCAWm4g zRvEos5Di4%WtR=gcWo$Wp-c4-`aQPB%m7dH5=uJX-*V5Sm{nQc9*Yvhb{fR#I0VY% zjQG{^<-R0O>PKGZp``B-hR)9{-r`Ju>~^`T4I*o3Xy{kfLo;ripo7CYCh%{+HOGUO z6-2EU8=nl-UNCzNntE1YySC_qibndvt`q@5Q1g7v`l2=%7@3@t4?9dbxb+3|^jNBN zqVppfRi#!QzJAer@(Ruf?kBk+xa#*RWU76U-ao^d(<NVZ*4>9#_5N<Uwx5rD!rkbC zErY-U>&@zq?Qrica%u*EXYkg<{1{7jrQhP0qQzU1W-ajc)Mf1{Mcqo*G)lPEXWl`B z{-Dbwb*!x>LM}_@vD5XdC&fQt*qE)aEh|d$htAKuRjsxfTSZvqbLc-SOK4~<SfDMy zbk0Xl13@zr2x@a=H070Iv4&wCmmf3iM*iU=k>qS6W9K$0MMS^2a3;K2oP;ElIQ*!b z)>;+x2RfmP92w#i-$w8g#$`W4?vC)Y>V$@e&5Q8O3qOT*QG<4s$I;gv5mIL*rzZW* zUa7p*;1+*`@2cm1&Wk+1P50-?k2tBNo2q#0vXX8=Qkkg$t@-QAd~|Yw1GG6hhY|); zI0-n9i`A55n8H<Qio=3PfCKcliJPDQd>!Xn&&F5LGF5oX%^jVui62F-q1^2oH{Pl( zny$h8youX#+U=#^DyH&%br<nwz>aBwsh}eG)RhBwH^1QR$&N*3s!aF!88@6V_=?po z05_?Z>C;nXv2&>xDBQjD+RAv);)E4s3VGq)8=<p*#7?XQm}IG(C*0eT8f&-<EMqko zK>Y-~xST_>LUpmUgh71dx`5<7aemEsM3Hw66%03sCWpISWX08huP`*@Cc!|FKoZm8 zu9E*|g&wEVC(6Ksv(l1dsKdo;U1eCuz0(4Lo>zk*wFA4&Hd*wP-Sm7O`{D|BspF8W zD^l3Xsy>7n<cx^^a`wG##}_LFR^9kkgXiy<`*1*QQH;+uVmx9UNx++>oSJk;e!CFp zf7VA{;7w;xwO90Po?o@8=`3$pTdbDk`!`bDasxe@+LHnwn*z1sY{^-I{Z&%3Thd*9 z8W+|oP93jSaewsPLIc|)ZyA>qhRsgZ)=lWu2BJZqHmU7)VC9!nW!BFFUT$3&n>Tek z)i+G=Mq+XiW$o0ra0F4av~W6{<;AuaBmb^D%pD$>tvVX!eHW(lSS@GJ%K}ro5J!sf z@^vaujr(R%`W@8+%H<c4NYwDpY`pAEE-#`^y1jsDkuI~n_^`3jH?#lk%g}|sWyPRD ztf*5ryA8;;CWkUrH+%u_DBqfcl;i8r5*ij1#5fbpSg@0!Zr@j3{q@j3oR4c3j}hXH zN7p!O%I4mBjaqO3dv6%@LtEVvsXQ^Q8~3OAzJg(z*8VkvH{Kqv8DPe;vKMVEf&)|Z z%A4{x#><!VOEmigYdKl66^ro8)KAe9`i1uGrex)z*Y^*2E2AU8UdU{}6PnA)z9aip zm_ENMLRYUH(6o5kp|bm<#87;$X<3)dLX^q*rmP{M0Ju%$TGAYxp84Hc`ZS-ryKS=v zoMWV%S1UPrQZgGdm^>6v;|&rre|152g=)Q_+@=ZF-V;TQG@C2ohp^M`)5wt0Qn&I4 zrB<RDue(o|KPsR0&QB<?hquWIwn`!}>TsSo#3c5xNh*MUR0$W568%EJJ|AsAnV+&9 z6s=ojrBNPI-*0FLXChBVJA=&QvwU3WFDbmW=k&`=h?)Xu?C{$m8|dpT7aA|N2HriV znp*J2wRv+^kEGHfq8UeQtLX|k{+w(|feUM;TgHS}{1N@O(}p|yau<(5XC`J2#M-+r zLXHD!4@-4t(8{74NfITs?Cf2t-0*rQ*q42q_Xqnk?I+5$Z@5~)6^i?;%<^+VAx<RK zC!X5!uIjM%tY}ww{tizEFC5*+W=dLft=$1G=6DQfbp+C?-~)(t8!#(4gh-2}SRNnh zF?&}c!(Qx7CZj>@T-*T329CXuA4IpI&whawK16~1N1LmvB5#Mpqe<4Pp$$cg#;;2D z(VbIMlgsTY&vAje3Jk-zBHarN-MCI&kn=J3F*%)Ou8Iv-!`GX;#!|W2CAJyyx(Jo% z@(wHSGN}O0OlUy0=?aPbR%%BuTP`6dEAXoHaZ9~OD~;ry<mxDhw(vlT*@zRh`zyC4 zUcSmZ3f&LYoFbQqwJuONO#bN5X^zecBqYo+31dz(>(ZP`zUEG36kFzAxp<iP>=>8K z`Jt~(2;X&k0&&^)Fu|nV_Rl1+5WvYZ4(b*k;#ys>9mNa18NFUz^O84&*}qt+b@}HJ zOYlYHPrVXu+CZSF94;5fHdTR-(6{k1tsaOImfus#Erlx#N(`GMl9D~t()D-E+RY9y zY5OO>>{QVT*PLRkzor&eAWon_?Ra?LhKW``Pt2mGAJ-r*uh)k+NL`@Gv=oOZPE7Th zX=h`V5P8x&tM=B_o@fS4d{Q0KG@{l%y)Oy!SmwZN_@~CMs=it)rM>+C{|V?H@pk&Y zMVnar?txUg@kM33YJ?VCV^hZ3{IHjI-nZZwkeUEiR7=zoN^X?lX!8K0izgc3vbi|2 z{#6DW+@QWB<Dl;<?gT^{b1#mGx1rgqyP8to<t|i)d11Ft)BAqKcp!GuKBattlfwc% zu;PsteK*x%Ezq?*89vt4q>xo%<}&QwlJtt~%O@WL(WlsnJNdXTjNru0dskM0Z%>5j z3lY*Ds-;|5j$a}y`(^zEH05->nken8LK}I#s^)pLj>yWVyfGQVRYJHK6meaUs|;~C zMgu7Z5Kh+MG6NB3VW7ysi%T$&J`yjEt2dBgsKTuoTy>mx<LsG%46jh=VKH7llN>wU zs2&y4T+-Z3qNVFpTv4079&ar-PmHLdcmfoj`wTY2Q0BdDS^kK2?@PrZw4A4_|3;zM z6eXSFk=$a)1hp1TPjJK_Xf38h+X<}#o{i0k4qT+61Vb{e%<$@=HdBTYnEsI5As$rU zAWD(yqo5Z?!kG-FRhdAC*rT9)XDX6Gfimp~@#p#eVB5G`J$$A+EaLE^pn^dNzq$yY zME0Ze-K=p6Af8^*eL+FNkcMYRK!hl%lj=XJKfkW9O!w^VgODcDbtLHJer<ViDs>~) zkb#O4^kBH?>jiI8K@bjhZ)Bs97BqxKngGd_nuZo%-XPS~DUwE`K4m+@0p&eeck?P} zvZUiCz5VCOeIXwe0!_1n(oJ`qVyTX}IM38B2<UJ}!Yhx5nqxdMN!s`CaOa5=(&m`X zCB`rvj;?hGqq0W@2;XLooFzPwKPvv)&>KV;V__7wz_kwC@72B>%&zX(&uOlz<zuEO zwqSJX7M3q=jt1U}%aBpHZyZRDAWH{|zadVmNjwxXy`Dd5djS8$JWZ?v!?GSaYR(k2 zpQT@T0}~!<GZh#3*pOVue3Ia4%*21>ohspBD8p5YiB*MEP{QX|OokQ;;G|QXM2@!R zpmU5*ziSIsl|e&=a!*21&!v*n=3+*o7eoU(PF%cAdnz7a_7?BWus!XO02h00!LA}+ z@?<w+f#bNM96Y+I$aAXjVFN)$gGqql6v5)~boH;4S=6;Z^N|0YF>CnmgxO0^nSNye zUuOqA5ab3Nxkk9|%*1(<FM>yzpGJ;Z=D%(4_9J5*&IYqxGrOzWe&6DVR^CIa=Gr~V zQ??G(;OJb)kxx;bHZMhouHDJep(f{<s>eitf=}V}tuH9}-*)`9hwA!yz@-Ow5-*;< z80iRLwfJpA&w#`4snbj;#ssM9QSSf8@E}jko_qj!cl9LqTQwmU)EccE@)=@9Db=8M z_q3DJdXh~ip82LCCehW$IV>lF1A=2>1$W%_$Ka=I!pBrS{5bc6`p%gxn&kNwq(?p5 zlr*78*<#R1$5zIVeQxe?v~b|UpB4YqN?uXqt|W{+U^q{(R6Tn7|FKc_e;f4&BY^4Z z$uj}0T?00?BSc`8I9h(ftXh_K%VMe{>2_Vr+2SUlbhLHql}g*3N$?Cgqz#nibAWyG zWfmoNFQaro@@b~J2#2r^pHU<6Q*XBBGJKs(E`)15EWklbp_s4fQ-~nxw#&)YuC&}$ zjPDg^(jrxPSEEZ&E`=cW679^bztdj#q>snhyVN(`)@AUe{|U(zJpP&Q$^8;OV!Po0 zm6AlI&<YTwF{oh;64*`TuJYv+PUQ(Pu`v-hR88MbxU(W>Q)<HjY0>g>2rZq%+v)~k z>psn$-y1f;bcZd*<vZFs3z@wTt{1#(PG9S&;S5kKB^)PIr`A;?#nw|xt4yk}Qf%p~ zE>~tprmD0)^3>ANBU9P(1%7aLc6O@2!eH!cobi_1H|de}58>4D6YHj%n5nMRX+yep zr7dzQHNcM5HVC7HasB*t#i7JKprre}S-t-eo%#*T!aT8aA9mnc=k`76n#?9!>t1(i zc14Hl^clbBTi1@(y}et0k%@<3_RbjY^=?G=CjjnlYx#Um<KBLK$Gh@OPPL_WHLn5Z z;-3KKJ)!gNiizDY*izZ<{f!Gz;*A3ZIk~yyLMMLt5nuf|0%eZ(+?S3sRif$viHhDU zYjc(Q7|iuXhO{8aL^0&WI2UAT1V4Uc%m$Q{37o&Oq;QUsIwt%^1=y#O3$$pSt+sgC zC*ff#-mRVuTy>Ou`3<B%h_P|_C^|=>7q+==y4Kcjvb|aLP|wjRn%3~7hw6JDl#jR{ z5F7iR9upkTwp=L3_1owmM?>Ixj@^NzVGBpx3v*7)foA~chN<4j_}uVj6%^R%nk-*5 zkP&2pr_q#r+U*bKlz6aqPR1b}vm@(2c1x`_zh05ATb`9pKH*ecCUx#jz&=D;<fcsP z^i+02oE@014<!52+u170Q*0^*RWou+rTSfU`PCgV(69JKgv?krgGFoG;pmY{eQI-o zwJ65+fk4KC>pZoSoTDxZRuSSK>%#MedQ|!g-&mdRySwO!J(zu=2IXd<vYSv>wC7c9 zArKtSWeas^TaWvr?E<IGz<hz|I49L)Y-YT`@qR&_SSL<r(T9omAC5`8D&Z<;tLw(Q zhV0C;fL4;S-Ee9`+mYs`7Kj8gi6X2e1hvkITr-%yeD$6=Df*8WOzh=K-w`sg_IL9U zKAM^a6RIo&i}|Oxv(qg^r-6|65+wKJ0K3%^pZiXr-heqQV_qe3CW&i_8xW^fceu%i zDb$dFv0>tHQa&Z`txAD&jjKHU&_AX#M;6GbohhjIfBB=B32Skr0w1((cJV^0-!_MF z^3jB|q-rI@%Fy>lI5fySFNo_DVvFx;`y6c5tbSUZ+9YcI`VJu}lb+*{9$cwK$~JsE z51>E&1Rxl4GfMvd)XiNbjHxqm-JDSB{6x=(*+tIXI_n%JO3n!+ZIY^wkD%*6odG$r zjKG&BtsjI23{JpxT{txjq8G+A#&(b1gtXG^ix#YvDc?K^^lL{iGE@=Rt{;^?^8cMr z{&=UF@#lszLVFeCdE6HitE`fh+F|lkKTpBB;KX>sI$P>(E1*`ahwNP{{Q4>9+JuT1 zbWKCcsBX=V4TmfBTflV<g8mBz!K-sem$YO$Y^O-MA5RXKz=u-?Qu#iQcmoAVq%3IA zm(xDu(TF7Bt{(N8VQ8MUARoanh>3?`jDQGY5;;6Q4Pp{F#VjFzg=Ra-h5)W8zGox6 zl4Il}EZsQD#^6i%dE+QQ!{In7go&3Rg=D(SA68O7y@5&~FBwZ2h+Qz+o(L<>aA{n< z%!`KsrU+h0rgIF_xK!6mG8(UK*BtoP=?LIUqqaeiO@D3fSSxW_kOTw*ow#Zlm<_IE z<=R6eZw<E5cs|x&YQrIs+zfGvcSINra1`W)Q{D|XvGBzo8>B*^;M4nrgx3tfBh0oq z42qwj8283r1ap{xf4C>_aO?p1^%{U;tF4f7WM~^btJ8AO5W({i2_Je2Ht$t#j-o+T zVRVJORV#eOPLS_mcCitvj`FbE5+qWG^fd-~P8DmB(PhDWw1$LW4)*IBdvdo-P5{Lw z9gB2(f&`ilv-inP92_b@;Qd&dExTg)ayr87ahvlVwLzO|$;sJeF*luH#Yrv!afp#; ztq*rc3w#~G2FVt#aU4#ngcNcUWo8PH2~bMd*1JnDB~!VsmAGNe0u#KwZtp~N2utFH zv=m=l9U{MGmK*sr*t#hwny)!avL9OkaRhS&z9Q@gG6`N?KeCyAe!371g^h>1_mk;u zi-xBhmy|i!4|fq>sh#;l#S0A5JL8HF7;(?(m0~|dutpsE*^}__#!->O6A{65fi;Qe z9HP)8qKqSe_ge&R%tf3*g;1o%$j`t}=ul(iz+HD`#~i+XU;;J0eteK@NV4`7STc8V zlibqL)-BdEkqtT@{n5?=OX#U07`hA|4b7$blT{(9eT-zJIOVdN(fhMOL({6HOJu7v z6{!-Hj}0W<H=>(aD?4FuQ}yDkJg_6-_&x99^5uTA6E-0-K~8;9vstjn``GB=3k`m4 zuRQqx_Z9I!*n97&rnYY38xN?*f{KVJRjCr1lmMZ5JT&Pg3B9QFARwVhjg8)f0Fkb= zkc0%OA=D!%B{V6a7b&5HD!u#VoaedsJ<lC?-0^*Pyzd|H_%4I8clKtkz1LV}t~KZU z&8ytg&o)RW84j@8ff!`8)_`ij-DemMsk-)XDwF|)^3pha<IDl-jMmw^QietB^|xA8 zg3_bWH)Q;}%=|oIFT8~V+KQs1qti6je|y*;ErK;i>Kg5C`D&IvkMu@9@!&WUj*7p_ zC`kXKT6l)%3fHG^dC;I>l_Ff*6w?FLrSvnRXNFMn>1Vjlte^_`86{CQflMqi-8XwK z<&0Em;_4rf6!JsV7Lsm~6Z1Gb-+Mrf7}M4r+~<Q5SqF1{dZnHpa6~s;tT)U=O-nQ0 zl{$PC)EnzPL@L61smVX1_&e>QBc%N^qa%xoFv+&tOxy~&=><`;l~+CGA#UuyMgURz z&zWwYQ9zYSG7A2p@C4PEb%uwzgZU!RjMzLHT^F~ZhI*{X2>9hCHxl8%edY@Ct{pFe zM>L=+LEAHX%(kk-vR^E9`3qN+D2tM%1+v(?1vkD6Vrl<g4YAI}N>=mj)PJ?o=`H!) zKqf{XNCDQk$mW0Qy0RI%>d$&~rh0VN(f4Z>tZH<44`Mt~7?j3{ogf#7Trq~Oro^P( z9&eS$=4~cSPQS+?_ESe2IOzM3^Ye%a=P+XQys-(`L>&oHk<Q9*=P@=_7%f`BItEs< zuyw;E_P`Bwv)pDq;16{t>c}|R6A{6>(uVchFUD3Ki!|jnnu8=+MK14KDp@S)fA06O zY*e3GEHQP9brlCX3KZ?n8Q$2*&D}O7u+hI*z~?7Ghsg^%9$D%Gsa-1Rh)`yBu)ZV1 z<J-UeIUuOrVNhcfu<x}V;>$C;^y8&w-qi7jnvfTTr5jsSqY@>SeflOx?lZIQfuIdj zN9j*O0S?hO+y<RN#;jK$b`JyUr#J%bLMkWQhg?H>B;WN-xdHg18_qp?H$X8W1vtcW zgu&(5$d%T(FjM|SHl?fucLnM1tp*#7XgjOtsi_MkgygP0)0N?a3`X{g6&2()d=wjN z+5|WK=I4=v#PZ(N3MCZ)p#akb#Ywn;b2Idn5nnRQn=H1yD92t2#skR%cgxm(GI*8u z1o3;yj7MfyvKB_gM6?Y+H=wj&)ZM#`cVw^JZ~s3&$f5@BFmnFS<n;e}a{8Zo`|nNa z|CiL8sByeJAP;Jp1~G_Q1~HtWWLvhBr*%8P>Ny4rt#LHP&itUlV3P-u>RQu5D3t|w zSG1NNhM$*jy91dA9*#H*=4J&I76pw{oA@LFAybdf3!qAD&vGHOKkEzJST$`L>fG7a z8QfN4v)(lEi~edqwv;Ow{$TtJ#kBMrsN7Ki?ID$kE`9Dr3drHW2__PjqS!CwJQ*`r z@)w7!X||H1Bd2bx)1H&36g>DW=KTTzg&Y@@Al>jv^R;$RQ2N(y)=@u$WGM&`oT~8A zDKOU;s`TMa{vsFlNy;V|XFWuK5^Sj;{@2cA<N%4}`PnV#-8}LkhmbK#3l##xfdNsy zD$aKp6w5oz2faa^ab|gz105+LS{BI(K0VP|)WyhSN%%tDDqePRjUr$_XPXB1fdyUr zj<wHqmZe+`%B)t`pc>Y$FwCJQ?9Or`mJ~$tUuR*5Ef2CAFIY}SJjG?}81&MYQX#S$ z8?8q#6|sYg=jEQ|C$F~1B=1UGk(ZR2g*uc0C#3kMWmc5j&U;X?S#84y#>Sj8Stka! ztrnQS=qh*jXh51D3d7R6O>x{K!jj(Rq^2N)_P%gHI2PV|L#zj4fJOUU^U303)01w{ z@pVhzAy;;cqXnMp9hd~7<i+>JJ>guaX#|rnLSY;m2ZcErD+(9SF`N^fis$v4jnoGj zI*eyXIDg0jxXUYEFujyj^-(cDd53xXJxLvEfVUR1tX8{Z@~nsF$PF+mHyb&@R(-Q5 zJsut7RlIW<8&!XQh++(AH)<g9QbIIG|BKo}{G+-k^OOLiC-5p!$;&oMl(H#=gog4d z<XmTK!$dQOSkwqrsL{Tpc;wLa4jNW<v%$Hf$&~nU7`u>!tf23HPU*7*OPzjaHVU2u zf3;6P^1qCkUy6sOpX{-7w{k!Z3^RBAQoA{_<E??bzyr>lMY+$9SH~;CBu-ug0Kgv% zZp1W;22WQ<Ij4;*V&A(63@Xs-iU663WBYd7flq`QeL(+<#CK5s?yC{Sz<eewjTS|L zti5J7a62w~SHCGe(acX80!)sc#KzfB)q?R^gQvGPC+CZYR_<2XGyLcDKE$c}{Xc+) ze?#`6G6ReeLHm#Ca215yr<bxaAqsJ`QxUpsC}WSe<I+aGQ=X7PMQGIC5J@o2OuC*8 z%%V9++I&FZ$YOz7QreclRCPFi#>3`c=qn`?x1S8)gO60L8MPhw3?EM0-0jF+lxTxb zjYu4c51e#BY>mF%ux@Ml5}n2N3D~}od>R)ZrSR^=_JwcmCp)*J5HsRQN<6iDrCmi@ z{wV%quvxksj?E0)z>eK}8Itf9kZ|Mkso$yXpVY$j={Lu#$D>afD4zfMm8O5IhQI!Y z4lzW~frft_|F3E|zK_Ui{13GNwEC-t{<`!JzTsbo8Rqou-oL<wh|9bSo(nVO<S?s~ z9)WQgEA#a*<4d09DvdLhlhGwD?jQzO82ed{5+BbSfibZv8>F>hh>2V-0;T=UqyTO3 z835vn5nz!7ngMd3IJ6i;x#BfGS3;7LQx6}x+!J|PSXdbMsrj;)rq&I2Js^D(21d6( z@cF2#efo{zF@5{_9g`Oc%7Gmq=lU30v@u*M-W&vGc6;+f7Y8e)sdmPLj{FlcGl>U* zd=D;-lh&2o^c9sVwQn78uoqfqab$l{0K8vJ>jJ7x(!J7H0>EoW^cBG?8~O-xXmIhr zZ1nWd?D$t*86tv8Ua?e_m^X19o%<9Eu{eLdN7Z^qS4KaNkzi=JoGJvOxsOJG!%YvG zy{`Ajl=njKf72c<^iILH`e}EJ4@x}G?l1HC82u5OS^D*o422bonPB1;=FtFOE=kI= zoUe!D;d>(OT&${)?1HY2&`Vb15%>Q<O+(J!L>bziz54&tnS&ECEsu1N>kqAn9hLO< z8E4lKb{yDx!U@&@6e^hu?i<&uxb}kc==QU#&oz$R(g%I*Z71mq6te3ydgadrO3!?E z!uR!<NmhSs$9mupCg>Z->*dSrY&dlv*@`v){6iWL&QD3qopFH@pn6WK4jQM(%W>U} zUr@o?ioPXzTD%~(2!*ej;N2c>%f%`UEW}<5pu!-d_9Eqbe{{We-O$9&aNMDLRT=Fa zpJj#_*ezH}jg{X)JQ%h=uP2`t&>M+e@Yy**dId=HABGfu)bzMNp8}G<fR3$h5f9Y& zA-Cu#A15C;o?l<_=)L?6*%F}6e8S}c)x=gx7iF;yx+2*FC3tHz>OD0|K8&ASpLw4A zDd(f0KzUY%&F%!NK`xe*h(S68irLm(^K3c!ap4MjAP8Tz^t6AggUu;}5YP7d!czz} z_}2t~S7{-R?KIXDsBM~|24;PQ>-xrz$K$$#v|3O6UYvF?N#7^Qtt_DPM!C*TQ0hg0 zVTZ2^P&lB{I=Y@K9Jv;F`^-n=uR%O#{@+c$@d^#T&|;q}&qlOG#$ugCR;;-^Y@J)M z?GA1=Pc@pHhy4bxOmsk%5=%F8bLGD(9cD@syE!9M1G2KCG2yQbC^DINQ>8Kk+xb%O zx4T)$g3C~52sQqmMRrjZ)L`)r&yphGy<>1PZ3wuW;)Ws_Jo=utnZG09VYOj7IY(dc zSD(3?6nxI;zc+CF&$#hF8t4C&8mG~}Xq@Lw1Kc>eok5A%Z8ys_cuCQ+bmCgtk4MM# zEWBZB)_Yl%g<0IA`LV$<{SpRohEM4lt&iS(`p0!|)UT0sn7xpfT*xT|C*nl-tn52* zrPU=nnW1S>t8Xh%?{@7`QMLrn!pMeOKN+5%hg!D&Hs6(;lvUjV&;I(kg*qP$D^As{ zEfla83W;$4H-+#&)dyF>ATq_iR)D(Z#;#!KI-+RwuPWdb9%L>i+^pRI?`=ymB?TqV z#b3w)7@Eve8_(j`)ZU}6F`pGO*5XVt#^Uv`!s<JjFBxb(;`SDu3(`Pax-WtRbcfr? zM5SL<gcp^|zfs?jy;W7wpH~Y>Oh^oLZ12UasJ+I-M`iXyRMz0rEbJ+&>z^2tkXK%D zA@aW$CSCNyJ#m{4JQj2d<c=l;h$v}1EnVI2WpCzPW1!*87r92tlEB1U-QeMBh4H$c zIA~Jakk6Nxw>_U$i@9r4vHtw_R1I~{X#X7rN6$=o7O^cQ6*e$4Qx?`4WLfO*kXhgL zbm79PlCaJ#EC;58%cxw%SP`4`?O~q}7xX^)K!ieY8v+bT!ZQ;;%TTpx*5kzQmGy1j z$Zyx#k>jtfsVVk-7AWb-R4G2`2$Y;stTgIVZ`F*XrnM)pL%~Zq+25|VZH*Acsn$Z^ z0d_tRma+zE%6!Qbt}u4Pl@nex&<ajQ87DJIgU?%<3#1%uMO5aHJT;CMOtBn~-Y@oY zm4l?SXxI>$t<f_FJVA2QI^0`w;OjXppk+PzJ@_5NyH?Jpar_=oj`uY9%g3+(vAur( zGM!}3z=#sfW4dj5t&M)d^z!koJIlIENsX${cgy$07aN0V6g6#DZVvubXCZJs)UYr# z=(h*_Jx=4ql;>%NM%Iz@I5QIXBAns+t=VVB`R?RAFt>rx<!f}8gn#f|&%8$py^&;0 z{NoJL{Eh4xdt}@j>1!RTf5A!V(d&wIHfe?)N<5E=9r1tc5new2Q%Akc@RI&bcfvUH z*SSC>)35)2{*M0cYTT}mlgt*nc?LtN_3Z0emPM|7_Ic{?&N;F_7PH;c<8pp3U}Y?I zlfWuMyOhX9f$agF82t8&8kc*tY#a#!v&WE83Ug<;EdHukrbLEUjCZav(f2Irz5?_z z-{8fr>l`06kM@icb5FK#P-X!=AD4KG%rt`?sA}55D#LMtN(OwA?t`cD5YC^Dx25Wd zv@txgk1N$oBm&~}d_hR5w=!zJ1+TU3auk9rj#9Vn8hl|N^P}S9SUO~oB9mBj{{xi| zyon3t9#-UPJ#>+TIz1MD91{j&xNzC4>Rz85)ff|xzK5EA#>j5_8_QoV8h<{X`Y%kE zU;gv_(YZ6PuT*@}7TsF7>h=IW8MD`ohsY*M_2z<{;EqZZ$f5H>1>>gju6sv-*@fht zvu$)XVNl9_^qn(z<K@0`XpUonQUlb~d?qebyg4KL?}Moq1Ve+TSDn|adP5H=*zhDF zMOiJeZas7$Hb?Jx_hqP7b{EMLFfE<^fyB~fS^_V;F~=Yv)iRv-Xe>puP%#rJ@ml78 zTOTs~-`s~#(!5NfXa6FGAVo_gP@O)E%&0euOrk&rHHJU<&ir+SendBe(D$&J!GF2u z^#7B$JBs|Ldrrka$%gqTiNRm)p~800aG<znm~Nv~yM8%`y1aQt5A|V*>D+(sGY&?+ zJHH}-i%I6?z*DatTCbuv%L{%olmt|U{{tKB`cG^y>U4wgHmVBA_yl=}P8imWhM@kq zfA&1eh`<zsI>s|up5dd9LyXc0ryDbNQ5>OMtEr-Kn(vZM*2cGfi*<y9{cJKUN#xRl z9{KX>axX7~J|x~^Milpxp|rerJ7^+wH#-ZhmR+&c!BvncG`Z>OqrcYqO=f0^`L_v+ z{FU^AWSrhuS55l&ccKS_0_j4H$czh_-u4Q5-;Xqg+qFd`V*kMF_yBYeDH>546(`G1 z>c0q7{R#1u(qgGyK7TcxBf+w#&@R~Brcd-KSQ;-v!R{9zbJEO@XUkfojyz5yaHCm) zUEjuPZ#KpcE_-|WU`&z;8I|Vsf(Ew1@)@;_zuLFZl5w>TC$eROC#tia;NJ)CEj=G} zHoUooe4T2#sVi=;Ji(4IEnQW4e2($Xz`H;0KV*rQFA3Sc+3ecpd#(~w6tpa0?oxDb zbcg~4Np-aifiUC&h#3gVA{{k}*Z5omCO@ng!p)g>sTHhOacIx2jM?$EDtn>d6u7ac zVV0>{kN>Eqh^@j7(ka(b5wp`j6eomVDIjgi0^_rs6_ZUv1X6+f%jAsC(*9n<keZBV zT2vKvHlH6*lOC>boh|KLi6pL}O7Oio$YK#@Nce6x++B8~?d5N;QTLM>wf>!@`EM=m zpDfLPcVS~n<heQ0FlHDq4%#go!Oi4A!Md{2V@at#-gd0eK4Hbu+r1pPjXFi1ZK45Q z5mN&<<b1Z(_$tN!i9ecrzqSD2p{*^V(JB=pP?ic_8SG?a=m@{d@R%X|%uDrO7@X)n zYJd5xoe{Wy`I<UofnS5gGuc&K!{j~uHx;^l5Le_N=DmR<M^n|IO@P_qe7U3!(YJyo zI@c^b%armj>X3EA#Gn;yMg#rb1Hs_mw}W^nYo;VcK#<YAFh<uv@+!SmFd<*X$0M`j zz5e+FLS=COB8}iMrYfGqx;)rCT&K;*TlXc146WEd{K<eCLYT48IhhBJcJx3@gH8FL z3<NQit)C1h-cps_OD$i~j(DZ^E2Vp!kH*Be^>T^5I&IYWD2}!#X}xesC7VSMbPaxJ z(Ij81>fU(muph3VGN_aYhLBaDg{~4e3<&5byf)x2;#E?a&r@m<cElpN1(Z0fu6K8r z2*Nx6k|c#!ck=;~SXfD{rEfpi$)5bq*?bE2{mIb06er7X+OlL4rVRw>*VU~@)NH5# zDy?`#zAc1!IcsJv+BIcb-^ok&Fz$5RDMw}QEXQUGdlJ!cA**E&SC!hD@ipW(NDZN8 z>1qbTq-}U|P)@<rc~dGfx}7VMqNS8Ju{h9LG8NCK@b(**1|h7sCOo%*Kb-D=?i9?@ z*^ZshixIb`2mUz*mNd~Cn(rRS<UMtF!lrO!t_^Tu8vs%IH(3wO*5A8DW<7Z}cB(vr zm%OhCyQJcqjV&nllt~imYwny!$0SM@Wmh27F2X9grBeoQ9qI6R+d#pt{=NMy@rsHG zt+*ZLi>auGeU%?AngHot+8v#Zx<s42x0zs9naUD>_IL?y2|xUk>Y6M!7Xs4$Q`$F3 zuQs2s%!eR*^%c}@$eiiIC2~DG=W5mH%oNG;;@1-KcMTjt6E}QaS%)N|$k`ce!B7N2 zOI}q$wl<!?UxkKOjkf3R9vQ2JVA7Yk`vRUsYgVNTY4EdfcMm{<QtjSMMh-c$-g6he z#NkBER|xLCDOz{^vuTHaX8VnLUS3cBc}?sKfI#H?jTU9*7Nl|i0AS1*O9LAlif=tR zI(5!C_`M=-=vVV)#jsd$Nk>y7uQ0>|V3)pdZG=sFw8f7xr&ukj;Gz0R!zcz(27S-^ zs4=#t>s7%x^KC5W(_Rjp*U<ceg0y!K2pEh@b^Src@$u!SEx?@<1FT}5RSvX66XiUs zuy!jc&V{3cm30??kAv|AH;<d4t8Q=FPmF~KLyJk{UWTeMtw)-(5x$ilHxO5{z>C|< zpo!|4d`O?Zaz&0duqsIC*ziqL$@Rk?FF8B2DPQ*ntKAR1EUKD<+^RyP(kI?C&it&G z05$UR;xImHy-bR=+Ov|`9)DNQ^yx#=A;*+kqHQwjhHtLmb^z(F+bq=ESZYaU#WD0c z!Rv>@?}NmynqltM!E_I~8pMKI_>>@k6uBL@rkyq_YL>gX<^3cuP{n&Zz^1T215{%P z+auNKtXGarDEV5Aaev&qiFb#%$CYTwS8CNS?uovSi^Ig*Zj<d9mnuEnSC_is^IphM z6yHci2FfhfM{$3nD>VkU%<eZ>k#dhx@as>8$7=$aA1{z^ED7bYA%q`TiHObKdNg8i zc0YcowxR1`zW<d5r7ZW<UtvoU+%fj4k20xlX?#*FIC4>^(rs&k_oQ3)_z#hmD{~Mt z7w)S?qU(FOjN76@&u*+H#s7Lxs|BPDmdV0NktKoKO?&{+ce|l_JD#*R#fi$#N&&*< zL^Wi4a*IU6ARNb<g{XSL5&uKoq6WP!Jb6RZda|#)08nP#?OL(tYQNg5dB}oYs*${G z=Owr*dJ)L>>uC42WY)KiK(T2*19Nz+rQxkbhc4ZwD>Li9XirHcT#1c_cmHmo$;9f; z{Q?JM*Mek{ncoj`Tt}YySaLUr6}u<=dNayqH#>TwfF8T8i}+?aGf)*ZAyE7wTrME) zdh6S81Bd}VVM4r{GD|czsxyr|{``kj;K-Q9xwqdcMf5R}8>+dM<9rR_34kx&tW8=Z zt~fvTlv~vGro!5dG5HJWs)ZR|duX5Q2RJ+7Y^{faxzj+E8|1q2&Du|pYzJHH3XWx@ zRXSI>ZNR1&hA=ez(3f1=1L`Q0iZAh}Tf(dI-rOyLR#eS0n*xP3V~SSs_$9fyd1_a- z`hn|$g137`&Fn>sH(v4!<{OmebEB@&06z`9r`!uVNIiFcPc>U%3?<5*+cuCS#BbAo zj|#E1g|^7o?(qbL<l%$5UJL@atUM%NYvjSdD$O_`qTUa}JoKapdP1BOHe>b_gS}+F zst8*vzXiYT6bO-7(kBm_k1k%Z<5W@)1d-f&2{01NzJ_{_Q$rq*Mo^YR>#XXdOqM{d zm1j4xiZ5&)YiH?;OK*m-SNiiOMt(J*Pq|b`6V~w&P61Ml)2VEJE7GDIr&=30Y)fqZ zg1|OS&i6n1RNwCt=nq{NH9P!0lhq5BXX}}+dn7=#NNsNw_vq=e|5CVQ{lcZcHjKz| zu_4bKCX)5g_$;srmS5GN+FNcol4q8S<0#gmsz#>GH|2h}aSfRbdL}DQe6i!Sg1^De z7S>rg0MShG+Rd`+(H3~rc{rNpjd&InhG_jX6B8BpjkF^1veCjlC0%fIS7$UjN4D4- z?I?ET>F<M`arWs+RR;aJd^lrP1v9>B{r0g2UfJ$WBifkL8WgtL-0)RPK3xOKnO9)8 z+2AoM3u|iyvUDEu_rfuYhs7{6Zw%-j6#4wEZ2;+}r6EAYDwNarbNTmpUSj!2gpQf1 z><5AbtQFZ60<bR4h<S^3zxX_yV}83VuPY+eJ;m&%VtmG%QT+`o8xC3^>1JQ7resDH zUMM)Ej7VwwibN^Q+gi_JUT(*Kn7s;@g=#LltnR&Dwqnc=s-Un1DpJEA6;*$HW|L#> z%^z$r@jYg_;vDu{y8X*qXIf$|VvQs9GrR%v4Wg#TrzI{k=xWDxOvAKCNEO8_!}+Le z#?SG4E`D<59&;1S#HknJl@;F_KBn6qFZL~tnByXc_fy!qt%U*k!MNlOB!O>pex|HI z9{gaWun)!GGq{29tR^)F3MHT+4a6{P;l$kZ#iWnh(H<A3jhj3mL)Hi)xklAFi>Rdk z<94;8Zn#y$f~RU^Nfj*PayPLoz3kO?=#t)=`fG=$x$x3%<W<w{Lrw~d;@y%_OM29c z>E@DB_Ks`Fi;C155dg2@!^^R+gdZU#9j^JoMp)`Cv+~NY6g}+R{%*|a0-cD>RMXA5 z>fW^5#T}fNonB}rR*q0i!E2<;g97VIflESWfH>P@pw!%M#Ba|Figrg1BfN=j#;zwC zzb(~mju<Q^3c^69Ks2n0o?nQq2-B4eS5nZsR}y4NgM92h0Lj!Ha0a-EPg9G71Ca&K z8!|o`4M^D3%ReWOIKpa4v3F)~-}^mB)|y|!ebTQ-V&8mR0&#IavB5Jty=DYhm%Itn zS1wi5d7t;pHpo!OxJ;BIav;c>q6N<t&Yz4zI>rINYkn<T8{o(@Ugea((9X;y3rwOZ z6pa~R_qHlai!WkJ40-@Dy)59ta4NTLlqufomQDpJ`)H?o;bnk9*(Z8iPgiso*2&2| z;Gj}HL@GNjjW|*z+NYnX|00J*Zd_4D{KwT;GvxR$&iudW#-A7u0{q4o{{&&$5C*@3 z15)ExeO_n3&KfT%%66IJa_?p{24$x~H0(?O+cKspn{VwFE2(m+liXOpv^-o|Vgtmd z)KI+@%kPi`O3z$bPJvLDQq--V1gnmXElD7FjF+*dzl~TK2GtJGN2T0HqX7XnKXP{r zO>#v7^p-w2&AMJ}`7)4hC6HLZPn)mu7c~wQ3~D&c4l%$_q~P6Z*zV^UA1YX_m7|9K zIJ&rOFnuaGBs<AN5j&tk%ie6&Z5rH)&4fIy=T3JgW>SSdecHVmbt;Oh4j8BjZ8b#X zEtOoGbj<;a^$jYrg>mu$jahE3d1M(%RRwzud4%Hz2dd(j&x$ALxyGcN#%&FNV6fCQ zi?)&bnWcHXWNc(SWQU+Q<0%roV7VA(HyQPl0j~y^XEd<zL0ug!H*j6{Rbg&;qiucX z^{@PzK)gFw)OfavVo4x|6c;qQH-un`v;IAs=RT3Vlyj8rY^FP%B^>6HzOxccrK~Fl z9LyyLzy#)IYdo0+Qsp|j^(}KZ6JeLSusXUtT{Y##s*WqJ-*HJUR=>!L@+THAc$fF5 zu&{DNCodrtp3OQ9)m!H6JfH+R*7zHdTj@ObqE!(ZB?{FNIj1G9t`5%&K?p$HYI1Y= zW_g7aT_^Q@sOBmY&|Gyb`$`?dTxVn}Du-)$DO+!!N77V~fVfwG-NpA6zrL=gWy&iS z^Rj#?ypz;yGAiR(!v2@AlM}_8d*%2}w1gR#M4B6RojC40T5{oe$|MH&oI=Q4>>iE5 zb4<cL`;22t$z#?Hsfu-o0^#xrgjSsAjBf0hTpR3`4^L*l!9Y#cO1w^_X(*XPJyC|! z8m|S~RO+==SKqdd$u0_S<LwrgqiF?bE!fx%^Q*eLCGm`L)h+1*jE^P9y#16Ll*g+# z<ZCDsZZ2T-!%b4VmWmgW9Q=M`Hv?}yIYFzKu=J}Cw(2UJ?MBS@I(jDf2m6gs#YwZL zRAa(DAA=vOxQeE6!T6h$mL>CwwZ!MG+L!V3b5O{3;sh&!O{q+a)!Nqel$Yk}({(m+ z=)}<C63hfyweqnr3~J0P+RGM@iMKW7b2OI5M#X0H6OQ?eX-AF{KN(EG5ZKudZbkiF zIKdPVQ?oo{glU!%sZWo;W<ddnwyc@Zr``&;ftSifOpFEP`D!h@p0u*3W48ixMttpV zfX%!Eg6#FbZT(2joMjU)zp=F}kyW*yH5k#H3H-{Qykpp&;$jk3Z`qeWUN_+i_R==V z2<{5BO_houm)7=>Di0p+?4~ss^?UMk=sQ)J)n=6=9&&AORq;-gf%;kPMKVD}g)e3p zBWdm*MuUWCOEDU91-T7FU~oSlMNG^`2HYXzsKsxM>yy>9-<@pc33w#fzdE91EzcG) zp*8iy3NE2!kk~$oeX+TTb#4aqaNT;~JqCA15}ze4#xL0DQKPu0hOy&84@e!cLHEIy zyPJp>vzvXm?%6AjVq*Bd=GF1W!i`4eA5dmt>jDS-tR?juM%%zc#=0yi*$Sr1RsF&( zc%Nr<naVmyNsTP2k$1EMR>j}zTeUMe$VZ2NY|HGe{87m<VBnzcjS@}X1{cETlV;}i zLz+(uCniL5GX&Qk<%*L#fR=e6PVz!qJHj~4bk}-aj|j4-yGRF)X**T*Kj)>pduqO; zEG`#s$@4OAYAI#nmaR(=r7D`Dn=ZyC6S8abmTT9l0(FR1-_wP6+=lmQmh;%a6!x<a z(W#c1I6zAI!iOn!lAVO9%9N`;w6EHrDxqj<)OFH*Y43i-v35uIXNqxBs_l3OLPg%{ z$xe6Zw0~rk7i$4wFB*444tAsh2kUCH>2$`h&T451OIKpkAc;~5qyE{t4lf!>>$Yo? zvOt`AFW7ZV-8Thpn@SH{DTq2we1xCV%&oWn+A6O;Z(9wNS6{Y@xX7{mVt2PJ*L=#@ z1y^M?m7u-jvaKU>_`(mY@{<9vQ+K((X>fFCsr%~QgXee9rW2L!>GNcH-}x<_Kv(lg zJCouV`POg|kABlyQVIaH{jr<&j7VLqanH`Uo%wL?=(paYDH<Ssi~Y*vO?R?+3>KML z@2Q*Oq$`dms!rlIU&1a`732HII4dZM*wjga8HI$@Ihb|(v}AQz9ExxV8TLkzjG?U6 zWG<N>{!i(FMs@|b?Ui(Y6RYo*B|wWK7wi*yo10g$q-X)VJ<KtgAU3vPe~Ikp2=snb z9T;X&#b~{RyHOk><B@1>AGOssuJY77V2KKGGF4wJN|V-XdRA55A8cZ@%&c8I=IYfD zaMGe%U)CS;bVy$e@s94oFJi&T!p4PNkpDG~3T=8^`2<4)VomTu<IZBh$f8+=ZCmfs zPcz-;zfY*rQ-tXkaQG}+_07Lc(J3pQ%j-N|;R?8(qu%d!Rg}A7r<JTGn7)8<m**~z zf{7<}=z2UNT!9p&A>mF?(?Dj;ysUlzHgYn}Oh{zLKQa#CT5qM2*LPI4UTI`%s<?=H zaTA&!+?iCCC6Z2XrYGL1TVBHM9o~x=`rc?@;d}n(Xqi=4&Bd=3{S$6W*Ijy-xR$JL zxNK`IB|j@^+IQ$|Fqz8l&OCCCITF-%0yot6i&_uj7mEqj;u}L|ySKN+tF7oJ6@y-% zRVB89c6lgbASbWug8MD%!m`|yO=+w><@v+v(H!&D5Y_bh)f3<F2wf}ucGYt13EJ5& zt}F58qk}xylllyA@`7+-7P!z)wk1;vAbB+-xQO(V;hSt@WVnG6vz&BT0TAS^EdG;W zDnf0+09c~A5Lp7V>VwQK2#~;@J{yy^cKQ8C8`cXitOU-ybaL%@i8|9okItz1g-IDP zO{o4hcE>U!%o_4TI4B>^hp5{as-LOo6a84$r&^`HnDpRV!e+_#ZPP({cF!A6rzW<f z)+Fy)7E_Y!5N&%4CJpOum8EuG)&2c^i0(&mc6Eg;we&cN+AZJ^TS3%_L3bL6m6sQ> zoek4=NLv;t1Fic9Ws;!syT?)>Pg}%`UGdX?ZM(?FRe6_>c4b0BX-gbH@28k-eAmWn z!KRa;Cs)HP>b=l9moA}>MG9ND`6LhZOf^SQS1rIhNL2mlEg*78g688IgqlT8LOgl$ zkp(JjOd_ZJnT>rbh=+YBU*G(kbAMVTzoMVgX^=NInVed6%OZ4isX29D5P>P<hXV-Z zkk&%Kxn*bnDek>HQ4fxj^xFs9DUu~epIRDEwdZvOdp^*0;xOJ3yI1bE<3AaqBr{YT zf(r2b`)2p($z3?IAMEgP(?X^Pen|E%6$u$W^ML(iP-`4`VItn4SDs^Od|<yOp=DH? zpp&qG6z=l`mq#yOwBIx8L7WyC?&SDI_*G=tiNbinkEo0F#TM#E`1ZGL&A8@WprS@A zy*#co$WCpWZ&K*a{^JqOkkfWQbt~V&^p9q{#fhMXy@2gF8@Eh)pM|n1=AvKittxIo zEl5`SzDbYh>`LJWy__d1;589!N%M89Z`mOUKkQzWef(k^WEyUyG8M5U$KWJWwk{}u zO|)}gdxcndZ0mF~SOPgkPK`;L;yP982{jzyFE*91!LF0xuNMqS<v$tX=nxIhv|3^V zvBqvhSF*XWZSCE!&C-!5sWVJ||B^N^9mU+uD1-PBlvQ@*T31nV!wOkgKO8>7Pc87w zhJbfH0{KARmxgi4F#m<>-8c6zI&AbnQlT8@8tLK^f)DLSO*sbO%im}0A#G*vl<wx7 z;5ARk2+5bUWE*pXSNM&(4@ixf#t>5)$2;9DTm0hS+Pxf;bt6lY!QwKX#*Fi}xo4kk zBj%I0^nnPUlIq@o9lflw4o>^P-GoF35Azmu?p%xXduz<Q$d6;a$)wDiUhy&Lh}vNX z@F0*wHtV84s}UURYM>1wAl($^Z3>1)U!0VWir0-S>q8SfeD+8;pp`uS%C`P0TFP}r zlOC?wJBAu`4t%ScCNa7PHo#*<Q{qN!P8r&H5(t$K!TLsDg5fQ+dx@5RrX^B41o(1$ z5E}|a?bHltZy}JBr?7gdi6wg>=H0YQHIT(-mRN^^SobQwmI=78wP>rn@`EB9nwd3) zHk7Am-R+qM+MMyw*Cz7VW;}p>4zHLxTL*FtQM+02;hTtxzA^@=3{B}DKs;paCz%M> zTdus|?;T#OUv$3moh9S<(YP{L><#dSHdh0N01F(S1%s=zTs5A6+B+_NcC6zug!`2| zT-%A}mZ?sa^u<@~xD(>9DyCnt7;P;j%!jSQ52aP^<KpWluB}?`9zC+~5%n7!b6N2j zb8)CDVD}VlBxMf)S5~>G2qYFX5l}u<x1A527xAvi#HAI>$Bl0EymEy3lP7ORR~!!r zcHP=iHSbuyw{X#Rpv=DO%%=>ks++j37!8+27yMyZT>OGQ5VB)DP|7i%XN{~s0oiUt z^!OGChTJaSGs4GIPJov=Ly>LmCN3B(4xOGZ&$qCHR<We_(6fl+uHD;T9MjA#+chOJ zy?8Rj>sz`}H{AwcP@KUoHpSYz5v1I6=~CK)<R!=KF(0YyH`D!2yRF~>H|`60gT21k z_(x83^OM!^{u~_CwUPs9jm-rJOX>;T_g+n<=Pl_rGaav*)ecGodqsVMH1^P7N}9lc zL@C(>^<`R98sbLuw6mu^`l6S4_x;q7<%G+7Qfmq^=CyH;#%(A`MY-s7WH1@zWzw;o z3_k#qEnY}v&pjV28><bC0k84IT2*#)m>O$6sDVC*Hw}ThJjF~Z8!xO|&#D*-u5(R$ z?o0k;IG$)d`OtGmyvMK{Ck*O~@vM#ZT$9GXXpSs)L9~nxp4N$ceWD43-7EN>l%yS$ zz_Xe;2J)kE7Z&wFDmhHDd0Iq$)#g&@misn*;+h3Z8v&}7M@kGzx$Nrr-8;NPa2oY~ zTe;FBTC;;q#jz;>GdyQqXijME9~|1^+y9vJQeT(Tk|5zT^P^wnnX&8Fz}2^~-v>8~ z*)+!w5HM|Tc}kti*xF&kLh`tN@i!}6AyV57uXMI$w6)6Iz9Y})I*^THfAXuAlrbAP zcMp(DWk07{x?$BE5>K&nb(+*N??`rKR;Q#?Ue|9+aO=Hw>SK;d!!Pp!utIE5X{XOA zkg_WL1CU9k)lw>QnCSRrbTGqnTUWHEVrc=9kt&mH8n|!)zlrRLHZ1+_Q`O>b8rSR3 zTvO_L%c_u&qzJ2Zx6z}#Yun>DHe;=)O}(TnVkk0G11(7#tj&<h2ETNjX)YMH!K0&6 z%lSlNo}LBlnf47x|0<y1c3+HcVTY$zXn)N9>!t#osv(L!AVCI}UoJc=oHbRs7L4GQ z%=Jl)?ti|-EQ(&lSGnApmyW-68eX&>)|H7%8L`Hd;j&3m+*_|EIVFh^laAjK@_b4x z1PecwWM^lQ8!m9#gbSBu7CC#RP|ArVY!{RJbqh_##_fl_gvtvBF<y@TCYiLrZkZ(b zZacbm9o1m?Do<7uIo9>Su%H4OuAJKIR$%zCzn}OkZe{@lUm$r={z8l81T#vuNWFzT z{gj9)vBh_3o0bTRv{S3b+#B%3+R$&883E!bJ99c_!?>G~2Z5dZS7!=XPx6B$@2VVf zH|n&0zMsKuH)UE}wufbB{9*9aK~FZtbl@m)%}iK0Q*$1^ds4Q5$^p&k&HkaRzmd2e z+i2mLgFW9#A41_Kezw$rDYs5Kpli2xbGG&t_;K4%hfYPxZL8`)>EJHMwd26K6@-Y_ z`nBEx7udJzuXHf&J>4YEheF9-9pqh4zl<qNiL%~v<a%_y`8qFM>`#X30lM7KzvOQF z%LTa{cumBLq{yd${QRwnltK^JpA0i)JOhhU&#F$e9jaZ&DhyJ?mvqJ1bUAilH6pi) z1NqR94(oa_#(NR+fi_TxC<Vm$qZ)3q*M5IO5NagF#o<>CU^mM=Sd62oCP~ReZTVDG z@ubq@)5p(io5hY+3nllKZ`R=Y**my>?mv{_H)|Cz(WH8trE~?EiEJc7rIP6^jxhpf zwRry+dy&Hsd%e!8Zc&o_S8agF?lt4er3joQ!8>!uz+nMus6j?#)<g9$6-WHUAQ6q{ z#8qlx;4O)yGFWPdnE-8}l#QdP1lp=$J3Ez*m;8-b5e6EJ_O+jqyxn8%v$Uc#sklkR zc`;Ka2wAqqWw^Jwvu9Opq3=mz9ak*IGH$(C++ehN5%<03bNykGivQz*^?pTUVk|bJ ztJVe9C;P<%&W}i+e-s&K-~-*ZjOyqYA}3bYR^$a4cA3Q+FDRa8fdxyxDA~pmveeVN zS@l&hh3T&QK#DMh-?%F0WuUtfesSk?WsT{urD}420iAE2omkw974t~7;8}PyIDNkp zs3-(agB?u@>}&T0|E{eYtox;kSzF^eSbCc4g0lB-@dz^k_}9+KXQEl;IXIm_*ZE#e zm7yg(!SUjCqS3Zv?Q-g|_rRoefaZ)_mPu;4Xdw=4{mE5BQ)-=!iFE&{Fx#*Dd7N-* z0BDV<MCJCNd2-vVO{mX3HTldGBlZdH<0@7r>5w&|4%_Z#W5+tj9Z!?T>W*xjM#oeR zRYV^;AP9w+0v{h9pFxew*PGgmv-0l0PgLBJ{JgfD+{vUN&O?p^^~K8N*i72Bf&c@` z`CfA9AWwwtLjQ*?LzdUER<wZ|0al^nGCZy}dHOP?r7MkH=jj0xIx+G!wyy2jlmt)L z5$_Uz)qsG**}6^T`x_nWo&^JtDY+?G+x2!G#HpuuaDVBFiF)gDnYVMru00P(=Hu;) zEfWS=1~%VIxhFer{h0D(HIx9*arOQV^n7&AzJiLlRMq5U60%3ae#tYkqW(QFB9d^y zz4k{*Mv2SLLg$2fvat!JDK$q)C!&R*+0iwo6*gyDe#<si?XizvLd7Efw#&EO;q;>r zMD7$@edI2>_f`u()H)lm0w}>l4J)KHTP7!qm;2)dRZBwX3z1J*TG2akR))mB5>b*7 z@h3wBmj-+_9wW3-W!*LX?2~J1<z~PpExlf=d@KiAa0hR;Wz-;C@_2|~-A0XC^>_Xr z3zt-<QKhx6aPchZzpd`U;v|!3D*6azgIAS(4~I4MSxY+;wK}Qu)=z8+P{?BBfSL1? zM=g1|!{6`=OZ{I`m;K+K#3*Pk8*Qe2RU~9?*VYmNCOXpFPYdo5qQS-{NqhB2b3<7% z)51G9Co)bKLyLG;j!3#@`tz#PfJXbVfmV5W*Rcl~sB&s6z!|g@%QF^f6GzVxb>;(D zW1O%#T8J)o4B>v}B~YKdYCoLW2{mxSIndjrvf{S%%S#*mtC(*o750nDRL8yLDWSrJ zSl`-u(^R5mgNN(pW|yWYSFM&K8+!sQU>$k!d2920-tZ+~8YN&nYDYz|xyvN_{6hg_ zBRPqov|B^;>{Ow>Bx>s)2C_dHE)U*k$FDApnm}B|G<;~MTUN!(^64i#`Z}jNQ0I83 zWcy5k8V$i-iI~t5KQ)Gq`C`<ot4t!OYNKBOh+utavAd;dMmFZ8K(~JpvSOz_N!byu zAnC*|cYzDbMl^Ij<*_R!T;R2&C$5b?Pl0x~J&luIs2^5W<AS+Q#tViBrH2W?eYVoT zz4R<wQ@nA8%ow2WECeE!vW0sB!?qfHh1jbuL?1!+Bka)|JBr!{%YC8TjXC3R;wXZu zlNMIh(b~AF<S$xD{$W%puQ&n|uw$=P*hi97rw8~HAGO@KE;ZMB00ODHDwpx`<<d7# z&;6Pl$)s+#eCNKpI^zSwraQM;*kAk_AE-Fm5xk1|h)KO*Nz!Ve($`!M|4^s9`8Rdi zf~5`8Cq^R!M11K<f0XLxg{GHIWr7f>wcEc>cz>t`%au()xz#1syxA3YB{BL=swoLZ z6BVnL&{0|z$LUB6CBYDk{IXE89~$Sg;JMh*Tz>puvSqi`8mINGWJ#;FaZ66Y8QFVy zIEKBIyiP4FfjNG|Zu?i_W`Fo+f0`8?8>q@aL!aao=h<d`Vbk#xdHMLZLk;Tr5Th<V zgZd>D-tZR()Ca>~ybum&uKju@eTty4KU^=5!TLyqy@9M17Ue_THPF9}O!)<0As6zB zLO&J$8L~{nH&L(sup;_W#%<T#$HMi`W7ar=o>j;^wIMfHWtKw5X>+s=aaqg;IEBp# zj(AI1qavNEX5Du8TztOD#g3`mb?>i&LAFI)=y6JtF_Tn8M}FWw9an9~X2cx2x3Gpg zQK)8{tbDoyYe-6qZ>?lIQRUm4U+Wnbspoyu9~w~k_6#=kcw*$}tbE=^pl1(nqr6|} zXZ^)vo{X)o!dvGsM_c~RD>o(+J})&Y|NTa}_m4Ty>s=E$pTL0+&KDkU{zEGALC!hE zpF$`9CiMK@g%&fM!v*~pLh*kSy7BiLNB>}e_54lJ=fB;!<;HLZr~1z#4gY+R|5#@6 z-(Q61Zx1K;iGid1Z=(J~nZM{;psu<9atkr~l<8mY+uU35Ap6>$l}Ga^6c*b3WDxr^ zcI!wonco>x0;%zQu^rzqG8Sil1n?b_yKv7{7=ic*p#-~(JNE^a)+=?T=M~el+di~= z2xZn6*K<$5{V`A#WDj>FxFPe>i|8PLUSoq)OkcXa)i7+kp;Wtn_h=%w0T^8v-jIcC z7B&0a2?t0X#yyCh^4<(xD~J!DdO;|jo_bDjg}F0ZmlXL$)#)`pG@pZG3vLj|08q$T zGL|ZHhqDmnImV=c>K$d`z4IT`J&y8!aqz471GP2E<d5KGw18nlL}9+VpAOUD<QI)T z>y}5?NSevZ1CXWSDl44njmI?oX?`@pw@WS+y#@2IAV?F%5}IUB`Wpwbg;)qBI1*Ib z%0_8E9omsir9ml`W*j6poRd$v9@TpsM}d4-Vb$+<UE1CtBI$G7lzrHMB+brH(uELn zvWMW~lXIdYCK)0lU$(PcyUoYP$fW)oAKx7omTR~8_|7E$b;VggM%PeO_%%I~HoLgD z*~Mrl?#6K;JB|*G=~c%dFiTtgb)Vz)?e{Lmx&qR2=GnzTY+OTp)5hR0e+<YTT861= z@2nhO9uuXd0zn`9YktVRJ&zolaGKD6C^}!6pk58;6QEnD6sFFj4|~i5C|~P!EeR<a zHA-Z*X)k?9;jTTyALbIVsEjeX+a<kjXaD>q=E$Ue;r8Z^;Ji2p?NJl%LisVeh{gvQ zdvy-anqd(WsvqZwc1u>pp+jpAMzVjs?~IHvsF`T0d}Cn^Ew>|{Yna8Aq`YXx!f4q1 z=Al*<B%der1XEtf_??}mFp8+$|H;5Tk3i~`+(q8`?Skt$!I>UUd`4cg>D*#%fs&xR zZ6-cDBa8Hi_C3xCjXW*xZCS5&>?f}^_7TlbO2!f8%8caaXo3VMve{|W&8@CGecGpA zRuZ4mk&^nclwUCLCqwKmD0C?~_WU1`16~zB8QgxHDs}Foq}Caq-eLR?2G9I|B-Z{n zXFv=pc9H2mQljJm{o&3bE)+&*v-imKCejgtne}Z^lZ6em6D>J(y{<JC@(R~(@bv`8 zTDw*fm?q=5-r-SN*3bAu{PVCMJ63)2CqqNEVZ)O*Wz)5Sx0EpAlN}RN^YHYOv1Cex zZHNE*in=Cts(N_9Y{K(J@6nl3R9wp&L!D3K9-k;;H<tKB6RkK9`JNgYrX-F95ZgF` z>V~{n&m4p`Z4YQ2D(oU|SfvBxC=#4oZfM(%`7SKK)vj9XC^KzBW3<h0bQ!%Yg7-Ex ztC<CNh>JrE-2@8FgJf-^*WVH}b}HHLWr=&XT*=3cyHyQt9F_0XM)>s87T0z#=E(!e zWtHPN4nU>xFai!DnaT9Q@{EU}VyDJcKF2eu;1?sx#P6xnHh%0}r|zxHVms!ojUUTf z=~jg)oz?F5_`$%zA8dp!4^44>>p1TWUZv5NLop3Fmei%Lu3?RdkdusF^q03XTb20k z0ysHeBL1TW0bdqc<;TPo#Y3ZLgC+8St<6n>#S#$Ft!^C<Dkq4to06-I;JCosMF3tB zmB7ah)BR-X9lDk8FQ}+|dck6Hcx7TRuXeHiph@4CrYA;Spm5ashpyoX6U><vHZGD? z>7rDEV|w(oA-9cebgPuzjW{i-ae}%xJUS*!9r8n6$*Bl-s^HjHn^&}}rT46WOix`b zwu4~#X5osbjJMma^<>fJ1uR}Amdf<n^*(?_VsmueU$oW@u7zFc@yzZUa;Khd#O(nr zV70J7BFQ?=35su(CZ-Tz^<4DCfW5}tIwKv(jNsY4mSCCKko*qAYU2g!*lk*Q0++9x zPpmMpPFu-r{j_YD^E6hMtw*orFkP_(;S=Oj-Jh36KGlfIO0NMuMWWIs*xGZn0K|;Q z1Gz>zM7iJG$F56!*|LW`PgmLMTFxdGKkGK=I$k>5LVcgU>u(30Dm2>iK+Cny<}GL1 za|fn2L|A2kTUMQkdUTcYTc;Rl%#n~Z#NCv(lPI69<42~X#4S-QGIM<msu4d7E#}>8 zD~ZH7a;^K>TB9m=8~Z%>kdWH0LRd3nlAzXZ^q05^z}>|mz+uP)JBJ8S30*5bn_&*y zT@m-LWSi;IRrBu8UY4n-G}*Kotu)9e{}R=~<w4a%FBhdP4K35$$1<GjK3HW1RxOTY zW9w{IZkWbs<W9znLZJ=mh!H+M5({}S?`|S_&aE_l{l<`rjRI!&Qcen0HDj&HYPCPu zM<zA-AX5g_-uY?BrvAuts1ba4PLtpgDplzdgmxSa*%`51J!^-tP8@Z+NhF55+OC<; zLgv)Bvlh32-OVXQczO~9+*E_ek{k+Z>DQ{c7=7YhBKhoD#V!mX5+wKa`qq=pM{)4n zLkWL?{P#+0D(s0F2zGNV1J_mdB1n0m`Br!6Ie}s~4EDuDF}*Xy2j2c3XVrJ?K#RHA zSmD0Fq6D-dR&s<bu@R^XQO4S0(Q`21S`$B50w$Wi0LNu?#%au}oWI6pXQ>c69&;|T zVX>woi;XrS9)!TT(7crdG*U{N$e|jmY@1SX<ocq_gALkop8NcSmYBfCX1Z%5TWrJ= zuWn2cwK64~sQY<ajvccPWA^7d-1~h}jAGT_?7ObHWVO8-eJMYsL+l-2A$))ECbca) zsRcWf%l|1J@;PU3%Io9kY8oXdj9cxxt~)`i_kn5*7LiYfE2|WhH9Tn0SE8}mi)b9y z))l6&_kNE~^~I2-l6Go5a>j#OempFd#G!MYzW=raTTkeJ4)2BF9I8vr<6m(lZenvg zcnh-av=!4H^@>Xx=KArvya+9ItH%^j{IeWGbYp#?&=^BOLK)VaXXGG&yL-2E189kz z{Iqw7sJa?yASmKzTGN~_#-T_wchfi8aW8Av7Iq>$l~B@ZOd&^Q=JEl($!kS%P%)#1 z({6CJt3Apgd&Hu(@+gM#tR8srLfwMj;C@I*%%oE;&;S|h-qLH?@ENV}X87VNo994^ zeqLVfL%6%OSW36LdLXd6$(ygXe_oxuiCIUWXN-3ngg;Jq7`fzsa3sI!ahGrO3Fs?d z6cq+Ej|dB!FE|AZ^~EU$+DK{{5mCV&sY_^7zX-m3Q(T8!7v$`;>$+02x8gK&f9m=; z9eGBL{|wbG;Kr=~N$VTE1Qrb**$f1E_l_rYbCt?-)tjo*B3h?-?$LHjz)KS)Nbf3X z#N^a#k+1?7Rzj5E)lam0{lh7k-^{0n{cyg6#NqGme`N1gA+Culau+O|W($HXQM0-= zfidB;Ts`(s%RN!(su>^O+$diSyX+d+XUc%258QBMbEnaFb8EIIWKZv{TS&5*isrNH z$OBZ_9l<JGQ#VUWjfP`v$m#1IXp)wRk`zRWXUZARJ{t3F5Gk@UabP`)S5Z2>x~Kjt zZhCyqaM7;8Wk0&zG;ceyL`NlLs^f<|2isVIS*P}@WaB}a?Bm<bSuaudXBp)YEr&6Y zcuXI$rb{KT0itH}kngJ!r-o=RVaJg(Y<)u{b5`HU@Er3g+Ogm>qyfj_<$0mKsPI@` z`r$3q6try08fw?Ib?UlZvCd)z#+LX526hA&rPnm5N1s!SHs#Nvk%<>3W{hxso)>lJ z-7sFZVV1BgC9;A3w4ef&*ozSFOg;0m#<MNf%;L*ww-K~Q4O|4s04m2vtLt1qU4c!5 zl^ZG)LUydgcfLZ8ryk6wh9>GROQ!XxihtwVSa)^ancYuwNr=e<60(POHEG;Zrian& zZTpGqQN1NHLH$?M!D}e6iG}87SfCjs&+={ZN5SNNvfEebGxNUlR1H_|%KQQXg|gY# zY{j1&X2D49NR-Be=gxkd`Fo*M%S;|Yfizs*j5xnx@#@+drpXPPJd=GQ_pIZ)6!Lo= z5AXlO-g`zhxvhP_xa<X4h=6pZN)S*WAblxKx&cBUw52o&y?0_oT3|s4kuE)jBvJx| zs#NJxLN6jc^xpAg?fvYt*E;X}?D32<&KT#*`;BssdCzOkJ6G;`mH$ug56I=_><(+a zDn^5LlQ&pfQy_l^SG1A#zRUe<jb!AVT2A<55u=+Y{;yL{pba`8e`=1tZh=gqcWgx~ z>T6@|AtCAT2kz88b4%~le{YVFfN9!<fMC0<!YK+1-jemdk5zJHPH^XasCT3;-R|7k z)~o2Ot!stH&*p`fX=z+3zSxfDw>M_gXn72$Xlpna+N#iK?@%0;mYt88X`}VlyWK`- zZyeFeES9412et4{#Y2DkBSD2!Dl6O9n&?~E@N1mq(GX+q+OfBIySgDR7NtL>9nz_+ zBK8fhZ(I<l-C8YJz+HAIL_xm1&yLvt{JgSDWY_C8F^Dt$k;yQ2#>a^Nsa^&Bby_re zb|-6akLprB$O_pC`N@!CeyG8E&_3tg9bI~<T4y&2azNis564X8vs$h*3&_ygTW2<r z8I?R&cqRY!v>|J1`BWrdHvi{YN<Fd^;f#I|D76e!6gRq_hcN4p3UCvb$qaA2e+zl9 zg8R8T?Qv$|x-lGY=A<KWSn7oJnM;WE9ORJ7`rDKJhl~TcZ-kDRQ_BZTf*v36-B8|8 z0M5AwJ^LD^4(m&8v`cnqvFRl%)H-y{v5Lg}kkX%Sxt#gpccm_RCB}mrYcAW3&uv0Y z+e1CUaJlY-f?nig4KKedBzNK_x}&f6N6i&_WrlxZT57$HXS;j0Me@C~XM85(|1n8^ zkH758y%T=)fQ@uUh{Z>Zsfph1rIrn;9zG<jf*U?Ak)K<Pn7y)1#A=c+i$2yBEl3=U zvOC+c^dBAvwZ^{;F5X6dsHCGU4GC<7YG#^`j*lC^9R!OuDIdBFqd(h*DmI$-)9~ZG z8O4(TVInD>|5a=@Dq=pUm{9K@z?XcR$b!)LP@PPNb|zsj8Z{TfG?e7na*^vpzjH)7 zBf0DN1lP{X9g@Qnv)AyNDQE+yyYoDrNU`|HzkJG)&mx^GIQeol3G&sN+DR_HCt^nU zIhWOEMgd&{f)h#}$-5DbNyy9!Zl`x%2~iyx&7oB4Re)3lCFQLEwft?(N=EJlp}#&} z{@ef2EkPkSQ|l8BQi4JtS*9rk2AfD^fVP3W>$a}92*dA^TpKT&khg2O;iNU(usabz znmhQ(AXBm(CANj`n9pQ?!5n~ExkuWf@5A?Q)&Kq@Yy6G<fAv64dxjL|S>W94Up$cc zKRwW#moWH#zon0oO)cKOwgUHiUF!UC?-<_rO=B(+=%1#M>$H<zGfsV|?`xE1Jn38F zU~>lXpDnd}U10!Zd%kHu1oMOD^5}9FVQ;RFL7h|?BYuBih2*K>6Ego!7q&P3)SGCv z^P<mau~Ue9nkZ%e_<`g<C-LVT`fej_>O@UC(>nl$9F2CVEwL;r-#!PX2U4I~u1=!P zWZ#g|PXk{UvP^$F@S4%XJ;PKiKt%5rwk|u4TRkn#5sOa7JXW>{(481wjQFd=Gt5IS zm-7mYkhH5sC6f#Cnu1uJa6W6I@^Wtzr;{et%caaN{IXcb|BtD1J>D5%@+ys;H3CDN zI1gZ1+3`hsq*oiy+LF~9;-$ZpbN0_2*a{9~$Uhi`59@jeZqF1_4<962j1pVtZTo)x zTH*Q?`G|CnexMXY-X`5qpi`Oxtp*QGm-xs>al4KgX`5A~ZNh-6^B&)F_qki;FKsZ> zPN##@$NtIdpF@5*(c&`Pt>c@n?tY2Bt$*h3Bk8}Mg}3KX<M2GUq9dD%p9~HG7sUR0 z%Kn^k@|PoU=PQPYa|~b4F`!?`oR>hhMO{aS%be%FeE}b+c8XT~S4-qGT-e|8Yi8&> zuYP%i<|UrHj+TG*3qSPi#nrq{=N*@I0hDdF?eDN)Pc&SI9`~H?&cgzDz!*31b<0AO z7inzXl22wwOhE@xElO<V^$Y}2N%FNTnP)w6N$-E7nY-E?Zr$67&0qS-0QjR0dAiGp z?5V#aUYdZDm7{b(_<?j<6w1ayNuO<$@nbI+!l(bR?rp3vY2-pD1UgeQl<sP?isWk$ zfrIe~-Jryo_2LYVk+qVRp-_T6$OYM^OJLX6x<XaUO>2v_6yP)a!x%7Y+;XMSpUkf8 z<zM}QP*&#%RJKu2*^<Sij$c&WnS~jDF4_v176asmfak-f<Q_SVt2T#|^1h8El$(r{ z&SfNUrJv^8p`6{kwXbx{1~~$X{aPn3))5r(wKryw9Sv&m73o!bZ4p;d+XvxXhGju} z=B@K`Q~f2Ld&Qs3j#T{wJ_h4aSacu}24)`m$#6#GD4M2nI$-Pna9~WoElil~T2m`d z@9Hm5$9Ywio014bog|`?pcAc3^_`Pnx0$t^t(6<AL-`n2_4Rz+8hbfMRzVm-Pm5gv zN6L=Ou!C;1pfL12#K8Jm(WhmJC_<kR3_=q_4J?5IC{}j4TmB~9k^p=2uH$Egun?g( zx(ye=T+lvTfzd+)1ATkfw~3It+KCI|7PtrbQwHWRfaqGSwZ)#_{s-hj<Lxuu$jePU za2^Xkx1B0(N4FDe<d}0(R6o=$z*)Rp;bDS_Mlc6=OnfqoLWE6E@%vilAhXiS+TU8~ zYFC;Bx7H~VRKC-$sxg;ESZNRSfoh~9nJFe<H*lvjv|lU83UcK`Vixqr+m&g?Jh?@C zTkheov6ZrBuy$`26gaEg&z|!>ZFA-6a!cdJ81&I$jHduuNyq5qmr-Mr6nfl;UsCJ_ zP)kIy2KGr;Dj!}mIzA!($2++eglpqOU~QcJw+F&$#mdI_Rz59Q?vIaQ!>Nk&C=F*Y za=Yp78D1n$Gry$IU{L$%v-YPxgV@r^C7+|ceKsq5E>_Ut(U;)LlhIpMbmr3ZgK--& zC!8iO!iIEf=0CHBjBl1WgGYXD)`mA&dIJd)8C(Uh=bE%ZetE?nrPZ-I+J$x@?$f8~ zQz?h>M<iC9>}byt_4aJ8hUt7u$#PXSYlxJC>(<nwT!A?~=Y2rj<J(cGA`EsOH*_f- zN%gf3Z~4V6*X@LOKbvH#DzJH1I|iupA~%)LQR_CQFZ2Ul5TAnc^qHJl;U@YTnq`^- zDb-(NXXo0oOM1L5Kh%@+;JG^0wrM(2x?(;#?>DS_?~e8O2xuBL+X_w`$=xHp*}Zkt z=CcG{4t<^CAh7C13s7nzoOtIco#ZQl?-Mk3@uBav0#|AmJi9+_qZe#3$w_V#cHSV1 z(WZGRfq3>N!RPYIIuSwRaFH*kMYV1`Xs`X437vGfdH&ci!E|ULRag65XOKMaj79@e z9EJxW=1j|zEhF3PlwBJRoq2D|7#xNS929i;a+hYz9=vy&w_KF+_8ZUkvMpPk@&B^B z9XSfANW(w0l?op=Z?s-ni+g1v@#0&Ilc?jB;I)`m7qmA=ppMfNJT-hf!7OF|5$0|# z0<2IpR$MN+GV6_8#cn;!O9`#d8<yZ+$O}#<;1|VKzqUMUb<2w+B2_u%*9={F795Cz zWpa~O%ZQ`hDRCb1_>Y9GZmF$8zmSPI#}Vne)VkDs#n?RL`~HuUP*++NZPF)cF#ma< zAO86_KPOFu`g|&!yu#B#?3jq-e<ozoHZA5iH$aB=)o(j~O&!g4PhB4UVNpiOGl-Eo zxw|D(PU~$Mt^3@_?W4iXx%7VF8KS6#w+InqYka9+8_Ueh^5y#6>1uNpG9s0Cz$J<5 zq&P6!YY{T#E3}y&>q|^tj&J97U)U`W7{gT$9ZCX#ZL@F{8}4!od((wwU{cbwJ}`u{ zS*~4|o8VKF-L3u38{{=MpC8;ETstOaLq44u_0NI%OXR3KD{z|RBxT)R$-+i5e@)iQ zXGe|UqGFxtdhh^y7gGcj<qS|feA@LO%{Ii*1&wV>6YcRW8wu$0Oj}8Fe`Efz>XRp* z%g1l1Blrdd>afUTkR4gdX2An!;WkunpMzA&%8y|%V-lX-eRV{K%~wwCu_<pJZSdq{ zse{UOHJ?#B)Y<vG5QMk<;GU=$^M{`dJMaD9K-bxE<-)y7&EGf92i7@%5RTFf?G3tV z&w5iI45@E(sJq-Tt1++PZYtNa*7ezHx3UiE0!LZ*yU7;pxz)})hSt`uCn6Ab7}G-_ zV<bC@l{6b9?3t9McMop@&bdqOKt{XDq5341z1P1;SLI#Q0VRYG@)PPCmJrn{@<0lj z%b(@g-Rbbi5^I&?SJ`415UVGUomA#m{30#-`)c4Fo6{Z7opKvLU-DYy2!b#*?d$`0 zHLWU!rW_IRwZ>%oynOFlvxqoYPOoh41~8~jBHrTb2Z)5hlw<f+$h8r#E!cY_;g1!Q zVjLil#}<=}b+fEj6O)LOh$cSIFR*TwJ=>O)GQP^PW12nQ;-1vLT6)N3GGHu~Fo)M2 zZ7>-i$7tC;+<(v<)dK7U+d{Ilaquy}J)Ox+-n6_<!J@PP;oV`|mAi;we;cIATib~8 zG{>gA+M|@MM#lH>dq*|JtXyjqyH{7h4w&~H;$={9)D_t>9J(~#vZ$yVBK9F8A#AW9 z@`OWX&(wnz2Mm6(gV>4?>m2l4aQ==js2%v0-S_CGx%Zu@WWmOOc+l}!8$8+Yb+WPZ zb`#@|!Tc53PPM>2>m_ZqbF72co9=A2g=-G9$~jEL%+rDI#@UYNPZSVPAWR|NNte-n zcDs2^Ijp|(1O|5Mdz#=TZ+P`8(QmY1Bvg9!1wLiOSEG`Y42qW05LWg2$)Ex64Uyi7 z^Ko~Hv6RO}MX$($vAF(OQT=TvQZgwI+?(c5rIf19(5E}vr=fa0%}ICk$;`9j&%SS$ zZc(nCy^WSQIQxLlace7J!DzmAaLGB>te3bx#JZ3?J7=2jIXwaYty2a~0-oW6llpJZ zU&<|^%vzh?6Qi-Ql`a@3{$wDVgY;M04yUtsGpZ)Eolk$o)upB$s;w=!vA7<JK%hI8 z(2;DyZfmd4tA6q4s+X{_ZoVyWOt-CMbJtsIld!F<@=gB+Q?ji!0%sJhui~`a*A`YD z`Rk=tMKw#eG!6?le}yU}c}EZK3{gVjZ%j~U=e&s0Bfe`2VVk-(>+itFW0SpH3TIh| zpN87*r70b!u2;{@ih<Fm^qL~(nKHcF)X0heZ{lm((Hnsb?IZujc-$7zvn{G-XeZ<I zIXg|jWld$~!QtVazLhCwS6;J6f=O$aeJo}_Yz6v-rTVWVExGFNT0BQaQLn>qk^W&L z?H@MUH$&42fP}e(vH2Z?%inzQ$<{U&*#3a_xs-)ZWk$LMpFS&ay&VmUqz=?T*vc^Y z&|;tT+rC%L;_3mmsp+1RH0kwA#Pqu$v)5mWdrEl}-!JNfry3?m(cA0RVjTUB?xc+S zd~X}|rclSyNjO_51wNB*mb6Qflty`G^pOIK8a1Hz39-Hj6Xb>z$6uS7`?1~_N3K@e zhuyfrR|MWa*XR8|)`ue#R<_DR>;*0A1mjn)#RiJ3Oy1DMcTt*|AyvF3!!PsG$pFKY ztJfNPA1bPUBdVYZzRhHCcFSaRb6B(zzd9&RSY_sq!;kDMM~y=hhLR2@6_gVfB~_(8 zW5kdb9`P?vamLT0)qF8dyCa0s<u!p$11nQ#*8wdh6cSlj`|q9NgR6O@y7wql4&r_c zP+8r}43N3kvz+POwtC*)M8y-$V2<aqKKz?GducUw$XZS^o^#vPC=u$kW$2KhXS`GW z<fW$e2<IeyVTS)}VRE39zpu-eH9<NI`;xCsXXnYC;^_xr@6>1~^GuS$$CC2$bg~;g z1OjgiK;G-;Jwrt5_H)B|-6gvF+)j$eU9q+8F1jIW>AYWWScf@G-+~BV1O0O}ddQ7F zT1l=s)>)^Jl3&~fH~F4^5lTVL?o_60#nd_xqp#WvRt7V|zl{lxZW|EDv<fm=!(s)Q zyLc7@Z46^J<{n#$rIX%2F>rXHZvAD~;eX$DeqiO|7gX82H=d$sW716aZdE7}U$M4! z1WYRzSZ~=zM3p|X;9e4{aegn;btgsuYc&6~OD>_=GZRcY3ss&;s++owKcLj*N*hpJ z`xUKnZF~Q)vhlF<ZdG;LPXJo`cFBYO-1R|beMOi_rVJaLBRbc^FEYzARLn4|RSB5k zq?{o=pzp^K>yLI^EDsxYE9;-MFB7Wu?~1cm(CzLRv%NC&CH|#Vnt+i~@q(K#qsl0_ zEIoZD5ZjJxH=k5CHpFcJ(f@OI@TWhxr}<BRfc_u)gZ~fRfyn>RA0R`iT>r-f{2v$a ze_X)-Z!W-KukSrf`D_6lv&aJmx*!MhozkDgsIOS#*D2_#AIb&i`HDidPi>fX9NY&F zue4A&0}maa)?!QDQAj}FlNh)(M3f2&3T>EQAyS<XxU9nR#YizLR=5u##YO=oXl+OD zBj~UH{R@JWS^>UY!_vOI&%Oy^?W4*ZdcG@u(>CY21r(g%4S{%%{CcTN`L{Tx>K&H8 z7m-5w8Ba3jABaeE6D;jRp6*?IJFzx&uWt6wAd-OvVrHrV-5cGoWqgzzHhL-(I^RA7 ztHf35Q>kF74g&W1eg3aIe#(4Ki81;Od&f+47PqM54-T`z17KP~eVG@seHOkHZ?#ID zkvhZE8bw!{`$rHA=@xwhEueo!2rhMoL0$c_z+YoxI|YA>iQR7E{&x^<h{dMj{9&wY z=1Q1qOW>#5zg}Rt<Ve>&dfGyIxbXvTvvReZ>8TTA+HmW$kCnz|u<8Ngfp1U^(Jmp| zu|!Kl5v(le{M?N*7MWbr8TdtI4(>tq>Z{8996D-jI`+ctfmT?Lzm;ZF`S=1W*v_(_ zLYKU`Px13ub?~B8cyKLfzI5|vmJN;GjJ1-0juaSEEyT)*DY2skD>9pdCZ?9N73y<Z zjMD7!y?6msU~y!4Rqtw7lgB~xg<($?jO2)Liybw{7~=F{Sh?K*hJXGPZpr_yEh1s} z#a?!~BwYZ%Kv60pR_|3>1l7uFmUF~P6$4}}&~r^%a&C7^z$Zi<VPuHOTkYU;J@ciu z^FD=!S{&dO359G5lRsv^eV9l2lV7Y5mv_b3wmuDa<sslM7FlLjth0a;jD<)e6Eu}c zZQHY!_V>@EMVxwhzHfq0q))1_?`X_BaulxI4DyDUVfpJ!j8!Jj56I`EShu}^$D3N) zLUVJPlDT6A#xCVs(LBT{+O$KTLPF|O7!L#!-%WNxNV{6R8z9PVIcU~L^=r-&^(d_< z=b{YA@7FpsxrbE5c1t2)?ai5e%r-6xIfeo@etil_6Q%4>S-9e@_;nnGmEy&Zx2yoW zd3KEbXfH@?u@?mpQM=+*<5v1Pg(%6q3g=+q575+W*J0W6?Vu5UJj))vrvA!=oyk*1 ziolFhsQxp$Vh<I+O>PrIWbA1%*1sm++cY#3W8SD?2C+*0S$0L|%HJHcR`pfMvMTB0 zlFXK%`mMlqt162}^RO^XuBoB6PH`^PSKLbiM2J;^0Q?0%aw%!C)`n*cKhk&2)e^k< zNAacjE(m}wex--o0oVIuk3UZ#!&l@RsYii8iJ7(V`C*z%Fv732$Jl(w$mqAkPPk&| zWr3hsR$4kM%caPdOtd?k-Zu3ao$E{F;B;c%1ia=4T|-C)Wq-cc+?N|nz3J0d7~f|k zSMppoOHoFX0=WediTp!(Gx$zJ_ccXV!p?>KeqfmxA-qq&z!v`w;EsM9de50%#>GUd ztI)JkqYcPia{{`_7<uM+Wn4m1W?*OIYC{I9x2zMMy=D?bEg9cF%_Hl>a0#*Wgk$_N zTuVXT*89V9knZ@Hi{{b>wdBaWVuwrLuz=iL(Z9Q30rJc0830l$2U+6TQ?fuSvkhPz zX2M?G%bpp+z{Pp?0Ff(46sPuD`mL(GrS1i#3YJ;8+DAFID>3D2d@R)RbMfQ38oz8} z$?xkPry&b}<jML`mhW|E?pWwVzi$AX)GPb&w(YG$+)=tqB8VaonR!K<=N%YqqD~{4 ztT7B{1J_<u{1`z{2m6oAJ$et9+{v!>7fnXR6-JuNT8R&Utl<8ouHBkI5CjEg1rh5d zZtg2=wTTx6F<IG^)$3j_tD!BOA5RLrw;QYmaKw6*ByTmQ<3ye}n#_5l@Q+d3&}9jR z5ju~Q`LA_xL4B=mf2mkwUB9}+?W#I*43FLVT|ew^L3-rmBs)Biv2`3l9zHrtR+(Q4 z)=VkO2bEX?EI8X{)?8fnjTCV~_cUyt;n~;jvJu8up(?Cp!Yf#;O-~9Q3>dk$zn*Y+ zd^;#<&&NQ^cXi=GLUKO>*eUncE%|#nSJ<xciyZHAX<vHtZalUlacA&u^3(0+&|BUk zss*die3W%59L~a3K@8j9kPOWJm^!t1a*@+~VI<?8uO?gIhuXWR6&v!~?$?5uv+Q*6 z5~EN@!kjsemDlsZ{z?r?{E;@577K2ht&jk??f|L2&|gOslc9^VQbSP`K0`88*Rx#8 z1Z-=Qzuht6-jS{>3RAo%cPyx(H15thW=sQ`E*SiZR2pZ2>x2$X+v&N09u(R#a}JR2 zM?{WAyk7)d`jSxsI*iv{Ic8!suBv@H{u{2?X4gVTeeHG9k!z?+hv9mXzZc-xiYZ-c zqtC}0b}F^#O0eE{9fiA@9K|7f)r8_9sTz?mf63gUb<KNz@jvRr$RVyYs(+kq0fyhO zIwDL?i;jP1f`-YGU(-Xv&d)x^t88YWE@jF_sou2;g%{oGy&LeO?9g--sI-C|XKeV* zf;W1jrZ#>epWYgK*Zvi#zjDdZS4Xa<JXpw{vqnY1k)NNQ5?Q<S{t`;uU`{oCSD+iT z5!ZB@YNBsnpON1Gt$e2q@c^G}Krpm;hP6$jH}t|;ZywP_t3=%fpc}laD+Y_Mz2!S2 zRmAjAjjN%sVJwO^V`y``yPgL)RLAqYF!V*iE(ggq;N#T1|M*Qs&*~uP7FbsPZnE`P zJiGGb*rr!o=C0ny7uU<C=IzRvizNu7bPP-Hd<qU=SAexSU17hMxfd7dFp@q$5@ucz z_5*HgIxLz^YrmfB?e3Rncu*$gZooz&;E19=BMXj3R7|&1e%!*>&fA|hT5D1r2zfss zzvmIlG{RePWv5yYV}5_J*({XNi<ER$tB#tb@a-=psj7`^9c$xR?uxn<rs-;2x8gD~ zI06CKc+bNf#U7zJTy~JY`VQO18(V{1z-8hKr5yU_=)4`edh49%?f++GZ*$?nU-MM= z(M((CJm3S-4Qz@v-t0AGsqCK&BgXlmEB@-GoR#x8%29j@9g{FsdK16-deyNZxI6`` z$zs)e6eml#Xi%7aBpY_?s?|EMA?Z5}ih%g`vfc#ml!$BCP^i7eO(1@v<-BGTLBop( zh9cT_=(@!!e-PYtro5<83A<SaH9AZ(xqZ8EUNsj#q`R!UVU?b1bIR-PM}l}IOiQh~ zaJ(`1rZzui%~F^}tVcnZC2Q&H;EHj@X#Pr5g%gjc@0<zN$E0*o-!M%e5G9{6X$06Y zcFkR6>9@o_^?d!h>qxNna=QOT4=Z}T?ATzQGc;XJ=X&m}slfw~zVKM0eV@GRlu$an z^Ob-#afB2RDyg$X-iUMM)=AGv*VWq<hVY^tlGVEnuW#LUI%&gFgP#TVZ}*hNF1qAm zwQ^XJ3CZzdBfB{ziPq8Yx}$v8EZZ~qD#{Bud(uQM^m}vI3m^N*1$8G7Y_#d=1Dzl5 znlASvRDyu=(;Th2iJ1UrlHEpGP$B$;N4n(j>XW5$4T_(b$tM`2NsMc67F9~QIL9;Q zK^wu{E99oy2<K@2&crco*F?KOY@nzV*N-wviBH2VPfr1*$7))J`chj+_c-%24cvro zh>66<D|f%*Pm-{2uuiLRPR%&YxcY8gN~9hlOY_K6a09h_#?(mie$ZS4ZEEv1vUQOk zT8%<Tno7NSR|r&)wyM$7XbW48X0IYD?;A4@QZe+%i=(o#MMq&N<oToCAWFz^`xSmo z;ydmbKW3Kf?CkyQ<QFZ-r$6q%D;CkU0uJRNeOmL*TCJn}f;Ye7x{h<r=1p4m<-bHe zYXFP&9_o1H=kIz2%)!MwHDArtnf>6AO~QJ5YmOka+O-F<tz82{uNzj2hHG01oe=_D zt{0Y6t(%mIYuBERJKahEG=8w#aJ({OcwWP#&EHqU(8vz?%Whj)rq$>m+$`ZIL!cH* zW11qyBR|hrr+=#5l*hl#X*DIZ&q~FRQAXV(&y|ZpC3}z@qUL|7Z-W%6p&GhY8kS9= ztahx78uf)n0zK@jx5kQ_Jzz>rV0IDqcY}^&a{|{_SHIK7#R)5H&OAM$SO9h^!R+YL zWXHVTE(rnzEAab^qV3_hyjNdaanBL$ROW7V@X?%WSGwr{0<hTtW^v}|notZA(J1st z-;FL9qJhWkuU51@1-Z7~EzOkj)K=bYa1^~@p8cwNOXut8l6O4Ip^m%&DpJr0xMDpI z^osg@YFb2kZnnJ>5#vw|T<)*JOYE3Bu~BKmBC&a76sy$NulMq8Ih&t+1&^o5Gb8FZ zXIZ5_Smzt46}F=)bx_%{waHY*wRb|W8dci7h#-=4Mi@THbT%=@nM@AO0r`_0sxdB! zi1+vT6_Pzp?dZX-rSXi@aCMzFpcVy?WrASsw)WXJ9;nTc-Rv^;P3N^&vJI#voaB#2 z1Oyc@O}2d*D6neM*y=$mDGQ~EK69&7u1st6U6i=Veu2`@+P`ymBG$~_xkp7wbm)Pl zr8P$`7Ie86`R+;SpkoQWwm;{by5Gu8w~BGmySK&&>dSF_oY^<qbeO$DnIF7Cf~-2@ z%8G2ujf%!s-bK{yKApSqCsO9wuOi6SE!l28-K%tT<Rd}!uUqGo;nH8$uJ5kI)-PTr zmo3sNb;T*kcH;4Y1$Z>alaG<g@^&_3WOkmVw;CpA$3CP6JtPk4zxIAn&gF6UW3_%; z;;zC?U;8e9MQ((v%#%fyWH2-Lj5B25nVEAy<&P7*GR`87Db*M}0C-<Sr0(JTg+ZML zgz};gcI9lxyChgLyukjU(NN86XWaIMNP0HK*BiHUzO~)l7Z+U8HQ(QgE|9|E$Y}(d zrx(QBnU!o-Y4F3dQ&yFxeR4~j?|sAuUU5_RCH54q_1p4z#hRFy$a6Ima6p$9Msu18 z8c~;3Sn-dYa6!UNOEZONX~`3Z?Js2YGdS$b#y2@hLko9!^AT*(in?00giVX8&3!V| zA%az6noP$w@YOGqTj=p^#VuQpIeIIBB^{Xr)DFo|xG=i7kbW@fDj*hAG=T7x2C?_k zL1l?M1(@_dZ_U0iYSo;lv3u#1kz}=q3mRyp%@;<qh@6=2w35{r`sY1anofH}eliqv zv5w;4uJ=#tFn+Z)&OMmS%M<g4Q8Bhe@YjfBsigJ|t52zi#O%@Z&)Jx2wGI@ULccFh z*F`y{6<-<5Fgpqavh4RZ9<bl~)!pWb1$!OA)w-@n=5*yV!B_ZW8Rf-lDalQqyY0jJ z&a8=sW?eFt#?byoW-e$|qT)B0p>NW3qj0}w?@m%yjI(cOQaTnDjJ!MghNa1SL_!MJ zoK;k%TvKIR2Yt-rN=_jr?6@JXB^QzIZ6{2N-Q18YxEXnb=69@-ii-LeoPb=vCV)2F zI``lgr6jbD!x<iUIuB;NU1HT@X;s$3)s^e6unUtbMA(w(=9Px4xuP^9e;`dEB$e1+ z-Cs<=R3D?IVh@^+_WeS;{m#5B9A@4kv!$2OQk@BM4qs{h%EbW9Q!w3!sYJB1%l5n9 zZJYQ3c$&~)Nbe;`0mP(PgEnMp<BAvdMe<9e9E!6R_5^nmyd^;QLq9R=r>ECuK9(K$ z^z?0z;3stGABky`G8r>!0)n~7JF}edv*$d;bt^crNy?(77CFv+DUm3Hg#0Ak$>e@R zmgEKCt+ASMx{S~Tj+00GE2kWFhfxLF_LZs|ubm`M*H5u?tBJPB8PPVzr1}BbM9kwf zDil^wpf8)2I;z~e?Bimxvz??HDn8&gQpRbPgGDA=bpswSueR;YdQl!gm=DoGDZku? z>oxz9U+l>S*}3PI<A15CRdz$@!`hEpVPx3HF@N~A$rBCZLus$&|1_9N#4l#jJF&Mb z268S96kOVz?~jVJ;r3m4b}F!Ky6vnhl!0~tx>o>8sYcosBAKnaBaMX(7D+(wLE_E3 zpX)HHJar9pab}RoY0P-<ofcu!<R*WpRdHFKPGUmW=4<5mJYK~Y=|bZ$JSIYfC4Z=C zxNDM&%@_S_Opo4J=z|4O#ev6(HHA7IK#DCS_3Gdi`>BKheIV*YmkTpq|G-PXTH8T! zpA?x3$H?(a{_9?2RY>kd4j4oX)w=!LjW3ynp68p%w=~HQwa_ZLCPDktBsHcDzU=ZG zsQJC^st5ScFNAztg3hqgsg8B8kOSvf5k8~FBmct5ql(r4+8u}GnD^YtP)F8rC%?84 z6u?MVvF(bXe@203lc9|^x+CQm4qZ0SJAaNjOdnJ7Z(|PpbIi_nOYfvMZv1cO|F^Xo zYyWfpJpY*g6QMV%W`j#hptsJ(Zv(w)8d{`Sh3)(E=zx@8_(k*>58rr>A4|h`SPd6C zhdHjxOO<wsRrao+$N-bvB>%*l8EeW0h(DM30)2@^Tn3SwO9E`N42hklS@~l_II!Cd zox;HBxQlzi5lFFRF?gI<<xd8kaogQG&(n^rfeeW6DXOw%C2aa6`TLuj9{>G8@Fg9Z zz{%2Zp`gfxoh$9P<@_md!*l$Y#Y|AQuA!BHZS3sLwG3?z?pRCt;Z#O9?+^KO*$|G* z7z6R-210Dp^KYi9VZ<?by18F|nYm1TzofI&lVt@6Il2SUEyjtDsny)eR^;6Z39uhp z6(IDNoqYc$18I5c*g@;n7Nsr52jR_?3|u0pmVgo^93~1=q1Le+cQ)<8`a!5Zr0mG` zn%rEGt$ptl1n8<RZaH6Uo;KHri^32Q1h7UuuHd(8st)7^PxxjLm^$I)G)pXV8S@3n z6WX6)phMZgIbd4G+v0NNeXoM3%s5<LQ6QK!=>K@eRu)Y46=m@{2o%fdHe{W(1*r(i zW;1>q#kme+Chz*JNtEM?^V3rn=e<e58X=dOxaXv0<33wRGDmkGM~m7<m0~&Lc(PoR z%#V;!UHb!gMrdEx{F3|14eOSo`Q<Q9{w*)>07o7M8AW@t!_n$#6O3naH2up+ERSC- z6rZ-)Gb(fJ&pFf+MZjcW`{d&VyX&7Bqrmd)D5>Q7*l-1sKt@JB8qn(7ra7arZV`j2 zTa=?^wzhv6S0SS<g}?Q+f`p!1Dt9H+<_$FW^TTmld3;VoIA5Db(4uS!W=uL0MdNe# z)qYOHr!RC|$;;cT`Bi<BVk<FKrY#rKKNHQ;Z%Emhr9{n33b1lg)J^L7Z{JF7L%V$} zS%)*`Q?(d{SlM!ljr)x28|^$ATLYoZWCZ~bGc&W&uT3ds<2AqulM`Q;Z;EdUe0*1@ zv*8$<0^HgzT-9E`rpVauTOZJJ0#dkw@xp_>;#*hbT6}lkxRuvw6W@<BDy~xFVOa#I zjbcXXLXTpKocHHl)$D!8t3RRw$G4_}?E(<5CsGMH<{pKmfXZ>Nd~8xe8Cn_4A!Fc7 z@g9tCBXnCjVlH4;P7`aO73;YEqpC)@a2hpeqN#kxbFwhSU8lc~f5Zz%&3H1laHJj* zPvR#j6xhGr7l|1~R7~hsPMz+NDx@b*xGGY)WZ0pzy_|>5MdZ?sl^eL^H4`m)OGJ>E zo!PA!f2nK%3OYbmI$R$$)%JQyt8_$G(Q?qAyvMnS6T%f1n&R-m+Iik-7<kk?fUe>a zg{x@8edvA~2n=}K?@t>YH<*AJsd;0!+D5I;uNEgwDh+o_ec_H6*jaZ32nX>q1g|pf zaduWI$-c0H7i<HsBeEz@zkXfz`LzrHl~Ystqvlf)CSnM+A>W}F!Y%?2j6QUiOoZ%~ z49lK<65_;jxSknIXD_-p!(-hihEL=RT%ei_?!AZ4`*fX4q1c?<?Cd;*K-49(h=C%4 zK}1AEh(0?x=&HNBLeA4gE~XD-<h)k5y%986UvUXQ2?Vu|)QllCw>70vh^6rS-;|#n z*M8r7W6fI;U+o@-8$aOy1yU^JR5sl6k`xJ60IBPCNOTehXY?XmxgDjqql%%gFwcik zt2Bp?4x@{8UR*edEgQGic@!(Jx0QsVc38~brsL&>5)}4(#2KPRdjngCN8gv)gV8c? z-A?=0NBJ;Eq6_*;6p^~|c>8&F$DWu+oWGwm^1L~XP?rAV`}Gx4Zj?A6)tg7T7FL_s zGd}5TYui@UqXjRF&6(Ypo3S~2C{5TlO&$xq<NqzFYj_nAb)+rCSdT1A;2pV`&pi<^ zHdrqDW03#IkcF`Se5kWbyJGvj%EkDkM@l$3-%Eg|)B!dcPjCsSrP`6CBPp#j)m z#WD&r>jlVuMO1Pg_wlLz-6PLF7G@3vt6)>IO&@?9qTsJq-*Jub`)XEdDk4?xA$djQ zkg>#JUBZ5R5lxgo&V!3m&RJC=ybZJa2mu(^UEfZU=<8y|k40>s^g4>I(5o`J0V{;w zPK?jQ@Z7;Oik4q?6<u6ZxX@M4W#)OMmhR#MuJIcMjv3Vyn!kVCt5r__QL}QJ)$1>P zDqr=K!LQ(!LYuQ+>kP$LV_J_cmX~gOOxSNzKwO<~)fsxz6@7XJ@sf!~iy!mylG;J% z+`^%jep2m2mPUcMusxkVtpf3_J>H36jn*yy$I36d1C+sNBWUUN^<I<G-74~rH{Igf z{*WFcLANBJ35)`MrU}3S<mHjyQ;+fk)TY|unogaLxia}fz0ktZH)@8i{XQmSGN{GE z@S0(iY_?$CV{2_bw8qzYF%(^WXXc2rJbXN1&_dT`Xx#kci&dyptFFG5OL?~pF_cAg zdO6F-7=PrTT(}*q4yUIHSi#(svj%cGZso9?uw6D}#Pz#A_6FHm)c!r52d>2nBN6Z= z97Xt@qirV3_;CGh5y3mRK><Jn;p2k8kTZr(AMBIsYNJFf@AR{!Ji&1{LRzj)W4wo` zYwF$~noKrk+_BeMi>ui@mOcf=TG@Y;Q9FHM!&%=>?yZC!;sQ`Os-0wV*D?|X#uciA zVN+n^EPBhtH`jqlSf>?HCOmx$es!V>7he7p95}Xlu-pUeM2saUcf?&5IMhUFbemYe z<gKQ|k(8R3PQ-lUMowCeq`M6Q5&y2oe)*#O(veV;eolC2-Qeh%#!}btMMrz%Qa^H; zkZWEXr0QOHwrWmaOQOxAh`P%`+tN(6^P#($3)}`CdDy#{=kOD6;|2K}M=rBJ8PXHj zaQb>3rz_y#N?*01zXpMSd!X67q2DlfASJsJyJY6JlfW-Y>B{U}O5NzwL!eYtEQ9qh z&-F*9MBeMKg`k3?$!t2KsZ^U)@;zM*53RkN>bdiFL=$UFtXF=B0ygS|cKl?8ucyKp z{-bOI<Q~04cu=t}G#6?71%ZbNsb4dg(F8)SPWEBAtTi#g%N4IiY(8#C*P~7Wm+CD0 zkKj_)D`T}%+J=c4=@E5RVC&Uz=!mu0!+^<~u{@>Y!K_`idhmr-2Pkxx6eTY6wKYgm zF9)prk;8<(nExbS{lf7wAmPUr{K>N3w}nt;C(OWTx_@*=Pc9)=BN-H2fP<Ulzfz>G z(lF6)>%~$SbBjBw2s?8zqgFsjq3>=%x!Ju^@wtq$+N>h=Nn;RXN<KIqi<S@K2c)>- z_B$uicg)2t?=G?}rb|kgRlUP4P}v;9RwsX(=}2#X-xft@qL2;QulK^2l*gzyk8+w! zbKaXy-U-ro=`%U*{A33I5}pn=a$=#_ApsRjoo_wSL(Q7aEnFZ1OaR)i$ItK2f5%Ve zSYLbqm$UHg;PirUpOWkX&GpfiKLw&oZ@-qmUu*-^TQZ%aCw6|VdQjikogEQHSbQhd zOFzZ4TuN`GV;MftB@;}2Gi4^s_ro&ZFKCpihyD7sm9Jpx@K-uQ`&vx=VHjW5xv+oy zL2HVy;QQw<w_2<{R>~D7mOCx93@!3}eWk0QR=W8#EBA^9$<=`;>v1+4JrJI@a0AuH z<}`>wF1GfCl5~TUn{dE~smX-spu)KEldBhVwhW)=xyaEj!I8UElgI@VAi;p>z%wx~ z56P-f)U6)1tqn3@bS4Kizj+FsQ`f3Y-%m=XB`?oK4u#N#+s-dUPmjL?4mBR;*e;2u z6!|+o^EIjRuefI7L-x_{JU`4rvbEC5cw6YTSH=Am-?<177OxDuKpQme&as)_&+K`S zQqxe*(Lb^!^;E4Sc=7k>-&XM;BTkVT)A6w9jtjesX%%4}xFP_)56541eRkwVPgP7} zlI^#1Qs-WxnSY)W<iGHDG}Hgmhrn<NUG&R2BlI6<&awX~?|51IR%)Qj5|0(nrfiT@ zk<+ZnNw2T_HsEBMGnE~8&yyy`qfgk^dNT1Mt&SY8Ko0xKaHx;8+UJC$m(*5Q_PHG> zu!XTeYOf6(-Z;%K5az_pNx_g%LGHnB?3Gu82D)`E_>0q}7w3TPuK{}!VN<0JRTigM z`f&{1d7>7Jf0uVlF<IXpVAQr@&Bro!{N6OrRoQ{uq$+=DnCkV$Ra7W^8dcdgD&~tM z5amcFllOpZoCHZ8I1w<n>!r=FY)NPX<OIgEG>im|U$o09T?QW#Q)&U_V;C`*kk5+> zgb}N|14Z4k98EIVp(kAnaFbWmaie0DyHEBShIG1;XB#`(3QoYD-*<^+9A#Q&Ai=LC z#!Z&<(oKcFQeLD+++gUe>{Ei~k~TsY8s-Wpn$w<FmV>uXRvz#mYEHt-KPRr1Drz_y zu=k^MJdr7x*hqP8Z}C}f_ZX7Xv~0Q0kI@ej_gn2l;h72|{AF1JGc#K7*OcxD-Qw0C zCpy1mlU}!dQe(Hj7R@J}d`{ep0^WSaqHGl#nGx~6Xe#BBLv~%^r=tC&OzuvN^*_4A zo#}5sWM()W`a`+0gK(JC>vvX=Q7vnhAIl6NbuQQ)JlcGpx9-jkyV1AZxO0*9qBYoV zFwcMsXEtkQW&uodR4jFNn=L}?@8y?U+G^xOt3su6s?u4ifc|6cV=kEntKq*+63zx- zD*BcPis$-NE{%+oHBdLi*zcj36-Zdm@wVtC6}WQ%x>5+;IbLwbqQJ3rUb%ADa%ev^ zQvy!g3Y|R5ORKM?*gA`G>n11_o9i;UPK0<k?GRb}tMk{p-dlvzx$!~P4u$g5>;}## zd7y9^TEcI<P$tE}u4KVV;CGU5Z|C#;9GtghQ-Yw0HP{ko`>;h_Wifo0?+HyI5VsQ! z&-1ll!vUj`0Phu(zs_nYfMI)%mm-&zrefyQQmB}!-~w&x7E#eVY?YKX9pe8?T+9M% zYjiT9lLGSPwcc-!ax-e9_YM+G*%+Vpq^OiwIY;Nc=1NO6A^L2YDkodN7zI45Shdl% zm>iGU=UGv6q%8YYeZ-Qjgfdbz%Y5MA?O-#?r~O=YOG*Fj?7gg>o|NV)HRou;Zpxrg zsA6!yk^l_n)mU6mkOmOuM$oX+vB|C5BzCc~!=;hCgk6f$!bj&j{?=~Iy!Mc<dm`%K zwkhR^$o}n-u0^YI3qFmR?ULAvwMkV6rc%jSLW5Nrnb+_uT=R5L>BHR2Bs_KN;Lvs} z&=b*JR0oS4OFc{1jw$t_MblMOZd{Cj5AUFlqa>Fyl#lh?z^|B9kBl<;-ahfGh%dJ6 zorU#p_pkZNJPj;OAWOw7^Ag?V@Jj7*5mcv=7{>yvk^_tGWKqMqYmkiddLc|(D`R3# zOwqp9QZjhu$F8#buXHNRJ?c*eiT5Yhm3$4bq&_7TvuSeIKETabUw5Qvj1;Rf0+EN- zl4mu4G7K0`BR?Dq!$a3T9EB67PMh1+#env;PPk+iV+mAEDZ$%frJ-bHwk7iUNcUza zqDE{Ry*unwy0K90SpH_u7g(vv1Xr(cd{XoS(|j2QFpf#H%Si#t2adRj+G`q0auei= zwq$Bs1}sZ0p|vSL8N|g0o|qD2AC88XbC_mV$!l2p7x;z14d|L%@bz~;8GbT&8}4_j zI3$m+K-~HmzugS7)`?E@R7lBia}=0{&0kqK2*QJcHzRwb#|IbJTj<QY$)5~+bj_}q zEfM2G_{`E|e(8X1YfMn}MArnzB({87a#3|#H=-`rIOgNL{<m2V_fCa&ULsB07M<DK z7C$gmvTJM*_>)1vaOv00!?T+0#2rLi{ZZn!k6BVS;rUuTA)cAg;&o`Cv6Qi#krUoM ztiN=OP%V74ezg6i?k7V+`$_QC@^V&j=gEQG4WTOmM|#MMr#$fH@J<l<sMDonWK~-G z8p@<+t12fq+}_!8kl)98=jdw<V;XSU>on8UW;4})2O-NnyPuGY?=2-62kNA>ZzvhI zvyNLCwLhjx)Btu|jnh6Mk!|DCBa<Bw5wmej>cOFTC4>f}qmR1|Vj<4LkUh7B$sN*| zk`6xwM9=_RD@Q~)66*Rgr7Oek&N%>ZwFp%Lt8orJ3tHIcTn-!QX$tBcrhj968htU) zH8-oh9lN&2oA^7&e1Qi&N`wCO=Q$4KdIE=mfaadqQ=5-Jewo!`T&;b`75Xlc1!f(i zxh0*??D-~;@hUw(j1!(ehBHmyAEYs2w0K33ffiT=Kekv#(hAy;>Tr1Ntn9_Ym*I4- z{C%X&&)e|mSVCl4r%&ACTNAz6uV;FH7)MrVHW^vS)Mk95OTl(fW1*qqxi8p(ti`?t zBld;^P9HG1MV`0YR<ZM9A^C0VnSQCJMr(UF_ZEETXq2>m4>50t-es>XA?4ch+kvUm zQrQ%+RJzw*XxOa)7m91?p!np3STz(M2t*oLoWK`bJ9(}-zjLjPRLm(rFcY>tI|v#J z5d-DV80b7l20GvKABfLCS*jOG)*zSn0(@Of>BQZlu=kHv@#dZ8zsRw<K5ZsR^^M&& z)JmRU;TMR9hz&BibzD1paec6HHYe0cBFN_H5KP8zc2Q191=|Z;ii#3d5OKXiTIzT1 z25Yz=+Jf}EgTce4pScY^hP}p;2~~9^=zvn<=8J*Cva5Z@NeT;nfTd8FzqTR2Sn8r9 z$`Vgy?ew3VijIg<xh>=CQm)HA;%u=9BbUNX<ct$j20h$BAm*S-kxfcZ9TCJTorJmY zJMtrv_sPX;y-kKJ^BJaE+^&$CHLpM425~r&7e6P36p~#f_Ph@4#6v8C5r%7eYMx^q z;>r)2SfzU)x#T$0rr69Yz#w1sooSX0w85D(;_y(ml-@&s&0*~JE~>bPUJ{PbY|z)e zq1k-okxr`xP{7tWT!C)R`ii~|Z&)Ga*=W;GhB}tU<D3Kg((m}p)+8B}b7IN8YE6!D z5yV)^B+B-3VQq_jz{$i#Nf}EP*zEIT*08*6*z`zR&H1&H3Dr+%@Nq%*abL04qm%0; z@Wag`)w5q!H+UN1+20OKellz;Wc+04oH@D-v}ucy?39GhZHB&0A<ZhY&!rcO9P&8s z%<hJrrFRUBX&s6)p$zG&;n=m~3BylF@G&O)xOeZ=U1UfbcWMF(a~i!vzr~+-IP;Ss zVpZUT?R{IpPll}ie_dnTgAV-V3=`b%30$r4w7RzYGyI2Y1if1$YZLpEVZoP<AV_W9 zy#cF3-Ly%Ogs*O#ysiA_OcMCMEvb?uOV;`JV}Gdjya>U;4|xp#HCGMxx#-EGYWOn= z{DGmyL4n=-ZH4Zo_T}S?rvepm+fY70=IMLm$%E?5XTtO=AQ}gG|1#mvr72VU&t}rE z`M=FH?)mS{M7#fThU0I>DgM7V&iyauqwDqpPV228n;Fqt2CYmBzL|sQ;eDoKtqs0~ zQWdc3$@PM|tw5D}eaL1uJbFvxZ~OkgH;(X^Yv{(U@x`61EaC0^$Ld9#zBfM!49$G` zlcCX{`7~N>)|js5zvfgYDm?hbyP(9~ZET5vNy=N~hFo1KFC!~2jrHSSSw>LSAP+d< z$r@nwF4?v||I*2o#6di3Z^E=Y0a0QM?ll<B$^g>hY)5c|zz|mU3#pc_WlJ^JcH7D4 zfv!BdZ1PyzbC-A>>Y8)PjEPq;>ouFQpvw8rr*k78b1K?}o19becO_&;BDr@LbrWed z=_XewrhUpMg6>|9%Ey>WWt(uvXZrBLEQuGyTCvd;Eo`9T=D2GUI=+{zqk{-ML@3p} z)7Gl(zo$BFw*?p;KE8i#HZG9Pp}kLHo7ASoO<!?uHttvl9kKUk&aTtj&x75BOHa0p zU0rr-lwr7v0fn;k#q%<-Jv;~^_I6#ZdmWWaR!Qh9KMXZ|MkKd`ELeWn>zPO(tot2U zjg=RXzkuL+hFrEAO1Ra5s?GP0rygwr^u3==Kg<|jaP)lX5J>x|BKwJY|HY9Q2`E!+ z@}O8&MN^*HYmK*HSV?#<><hZ${22kgP%9>)D6mNgY$bH9v{j#96Dtr81{=#S1lh8# zpZsJvJ6_jjzMl`dzb{}y87RH#eAMcY+vhqZk>H#?nk_~t;Pkb>wEf$ic<Qb1=%;7S zaa%?DGF^w-fY!hz!88)uj9AvyQj&|X-SYQyHO=>$aHA~sw`+aHQ0cUo4c-h~e^P~8 ziET+%La}VWs`3y5lI%V8?eHzfAlly_l*g4c^47#Qqh}fYDf~>r?=R7(r{``xzL4r> zkVs{<^mTI8(9K^hfo5&-_Esv$^B8n!9?z*8n6$(hd;KPL;|qoA8wo32S#0C3b+~dh ztfK2D!;1@N5}fGcoIQV6!SS!fx`B(`f6bn(KZ@G_ceQTj-xb|X(UOdGO=#!xUoOMx z-=14s|L&q!mY<XRw?^HWsE7akAZO^HRtKSR;?0Ntljjq1y!^6TJS((Odq$T`6TS}y zNTz)*M0wPJSlg1<r6>$A_Zqc`(7Z!AMsCx|kb231(&njpG!A~<=F+fH1oCsE^~`g? zbj#q;8vcj0lggV$(iI3+d?Tz1yG!$_c{<&!)r-qrf52X2jjOO;vmW{N!krQ8dlkNu z7q#@uxkz@Tim#YSFPsom@wH-8t*e3**u**tL)F{g<L3U|O{IMJlxN3bab<e3W~{d} zr=p3~L|c&M^9a0x&d=iN3AFm9Eg6;*52ovMUY}d>AT$i@mubsM!|p+#!_4q)<n1lk zE{OnmX4>9Y$33*m$<^YJX3eyVtJ8c@dk>grb-g?*gL-|Orr8vteK!wQY<>q5Zy|J3 zuWgeeh1;(84l+9%un+x#cB>$aVtmUYF*+9S08(7z@FXW@s%#s|@a5`i!1z1UClL|b z8mnINbUM^Q?vVb`m^HUQ+Z!*xk>yfyKYo~M)QUE?6h(n-3>BG;(_`y{P`HRWoIJDf z;accs(qel3@KshDrqX+a@fv5v!fmJ<_UoP*Ev3<C$5;72?M2F}pXx0nUHkqX;&<8X z;*Lmo6FVz<TmWPKDfo=GcF?$D;$<LMZ?Rh~Z(%I*WDqhDEzHXH5CWjUZ~!sru~zAk zoc$=9r6j8yphg3~b1uUh-d`@_hbre7#NjAlaD4XhtsajL20Jvb<x{GWt8aL=LRH#e zjeZEmw*@qbLs=R75QA{FS`@Ep3=T5@pU;*gfFTp?IlRW`x;j+D%zOM^COzsC##&<e z^+c8F+iTTJCwE@GS^wwHOnvJomjwRnmp}jUT9s+cvEule^mbf52M53Eliz<oRyAzc zz8%L=8Elxd7i^H9N%ct(Se(&`4y{i<L0Q%2DU`hsGAIlJ@E@Y#w@gfwMXp;KE-AAV zidV~fDtg&#j8rXfDq7%!6++dUr*5Pa)GPGsiGUA94EDsVL_#*JlFJSqeYZm$5LA<{ zP@sWec)(y1Oeg%bT*+X~1!0Y;<CFk_*^<?SLs82F`}fr$s%HZ~!`@#QQ**wG8mS9M z9Fpv+-sfSB@~Q9uuT8o_0&}3b$=F_3hgaJKu40ac+0iH6^HKQFY5)KQKltO)jX%NF z|KNh(zP3I(CDg@}G`Kk|h_i1>v8K_qjPMB#9f=8E_h6ISe4X@y3(uEKUT4)R^oL-F zj3VX1-55D{KN|U75=H`;R8>}x0cFJ`o!2VXapK~nGF#&iUbCz${SKkDk>)jocz)7B zMasOPEINeg42PUO+FJB`@lul#*G=h*D-LPckx&lpY*&RU&;%AUlcYIdgFikbF9}d- zamlS{ez8xH-y<SyL(!3p{}*HL9oJ;D{fiQ@D<UGjDIiD}Bp}@qdWkUvBs7(t(5uqo zYe8C2YNSgK2?<g{6;Ke6(1Z|>E?_93iP90gkMG{+bKm{D=iIaNPm-BQo@8d8nOSRn z*Y^vOK6yAyR9q(Vs)xZ4&VwB!g_#)XR)tsYDkVaY0pWQKa9D>dwxr2&X9GQ`Po#@n zO_T?OoF$)#Uf6{g?m|okuEP8kPNy^+DRm7x`t*@1ieY(6Gx_wJI%Wq7FK$WcwrD)2 zdIsf3$bBd+x-HxkTvb*-V~i?j^pvQjktz+9<LgcAGClk|MZsK`EWnRA`qVg&Hw@d~ z{kbN#a8~7lGdm7*26XvQ+b*ty9VC7ASidOOM^g$P*_uwlPV!ru%Kh{K_hfnG8s=k1 z9;`2-3#~Zt#nNTOo~Tc+byujny*F?;c95tB@fq|m<i>jB%lzxz5R^s4RKn03%JP}7 zpS>iOZ3y#{2{}oswpMb8!_k|ygrWDdQ{83(=FlS$d)7f*ItS=7MmSu2XFK~CRb;(( z6v}Wh{>@_d)9vNl-1%U`J%_UN`#E^9U0!4V=aqsd4;K}-j*v_IvJqnYY8jDah~<6G z4;*JZ(gY%m?Vb!(YCOI1E`;Js%*ir}xIG@=NHISrGcrrGXH9<rR-?l-xi~^k{9J3h zReHYMqmQ4_V10Ld4R`q*NbJyFJ&v6nbonI5dSv|iYraaX5pMbvNaj%RIL?zDBze-I z_hYsW=Yym7_HnHoAW6_^+%<NP?1}#zW48YoV_eJ`kl>#d`=Syapkmx7c96^wc1z%* zICc(@gwpn_l?zy`*q@0-_<Lf>{a+@QEsl5|bnzU+51xtO`~c&5A13-|D95n4vU4D* zGhxdmv;Ylgo|Lj~ktGw?#{uGIw-J3PD$1S@$<Bv#-vaaCZl49oV6?(FJgkG-QfS7Z zW2SyZ)~*R|Vu7z4)JGOlASh|064cLJUkQ$o^2V~cY5o2S?l*WX(aPgyw0Wv2hZ}u2 zZo^bxyj-Px-+qo>Y9(B19@=A4%VBvu>v&{@^5-!n<T`gl&KN)2w{c>!EgK~Hbk2Xy zS<L^MwCp<P!sA&zS<sFD<?pFj{%`wAJN}%|M_2~S|7SkprNvqCPDgNN>>zHPjgbFx zuD54L?m{@qZe0l<bfab#^(CcA(1;Y=Z%z>R(fYstIBx0`NcN<5{8{paZi<-g7H;AU zND`AB@?Un`(*+L61&*t?yLCUm`_B$M{li@8$s+L*;Z3~V5E!{FIHp$ILx}o{kNFzB z|F1=h@b{u+R@EooueYoi96+JTMh=)K-=*WQ?4aXUfvF4Jn_vEVt%Fu(Sp0hwNB;Mt zX#3}8z9oR$Jq?mQZV+k7p)%lHi<4&q77DGSIPzK0<>URL+VQMlj(^SPL+sYL(;OhF z<J*aI)9!aq%ly429Q%7sxW$JPIRlb8Az~0EV(?cr1dT(S0oK8l|9%WP{=O5nX=9pq zRdDcdB8DixVq@_>Syk$?XD|U-Cs|#NQCH8jevu1Cz#30tHw3_x^q8U`FE3e*#QZ{h zN-zHMsHMSfWQvqvE5f9fI998BcHZW>uJ-d!=J5GopT|y4_i(SEhq!PW=MM{F#CScc zURp{sL;US{i4O2poqZ#x3k|XHadCQiY2Perxs;+~zB+xGFl#F(vPZA!$uaU3q}1Wt zA$-J-As+8P`!4K^`&G4-RvRr5L|w`d-|bzBq342iDcu@%D3WAS&(Q{eOgS!7w@y@4 z>D0Bq8J$51>WiM$b$LC55y(szAozDTy?1LZ@hznI+Paf>#==~JsmuGw$S2S4V{<-_ z<Utqf>HRy-VOl~6&9!#1iFPqMO%Yg-5zNR!hls3`&Vl~l+d2KREWrG!doM2X?Z)L< z-XYE5a=1alfVqAS_kkP41Jc9|a|09w^O5y>m3Sb_B7)YlR~mh_1bnj2gQ3B6GrFCS zBA%4HHe$F$TS@)vPE%wgNok?hT}Go2J%b#|6H)>XQ^g|>pj?26vzANca9C}Ps|LbJ zccHJK`P;0_v_oxzgd$Hep}5adKG26&UmPDfYP*;$?`kx0e#%VAtuq;*pujZ5{j~X! z8dTyIP??Ta=dL7n7?q|zjY*H8_Beh|oIDY*8u62ZBVTjqZx(DV?WjIPFo7bx?(cuL zR>Lg}^R;@!s?;u%qH2-XtV=!bVu#8Er}UC2yIP)<;;_<~y}0DzD6(0CK{gY*p89$b z)(^jZr&ZUG^Fai69&g*lA_zo7GJ~l~8<0_dS>*IJZnK%HqKp6!)vw*V82{M$6^{S> zVPy;}pWVa2QS=Hi-D*sVe9AYgnBwJ!qL(alz~FuDL6CW;LzgqJ4U8B|upOj25%jJ! z6!1qYNEy1Yw}u$7f~+!tY{SqnC*u0b<9=M22hr2VdTCHy4ZTQkvQfF_fU50WwvpiA zBoJ~X5#X3-%N+RXZ0jMPruso9!8$7s{nvU(m;=GwoVFV<dB_~HFglP+99tc0@UBTr zzE+cyP!u`7-S<ua>5Q9oj*nlor6x#9i;>=b%5w4Yo#D%JL}!fU#pBy0A#hK(g@pyF z>HPdB*X{wTRG0Hm;MfNe93;qmWaMkeb@sl*wGb{0N{3rr66W4koih{S>OnzRYN@ea z-a6WDgpa~2FNO;eVrbQc5PiyI?E@zt5$WX*A7D_ZB~<Q`4M+FkE`Q%vF<4g!*A<el zg08M^bME8NaRK1JH}$7rJ@UyuHnKz1Ys7if1Dh%XN0b_I$wnSfERmL0FxM`_zf&-T z%e18YLd&yx(mH=$wdeqoM>=GZVO7KV_L|icpV$vdwcVZwDm?_3n^S!q_p0wOKjtS` zccIS9WPF2jW?2B8gC|*pFNk7$+xKaU!vDn4iS*i?y)5DUahP(c*(qz8D2)za>n{lB z(nXlw8Y{K;l2+7VrsjVfliW88KDAwt9XlhN)m@}}vnR=t{&MVPx0zDuotem0=D;IQ ziv32psF?CBz|<~^d&LhDz=&Qr{ylxUV1A>p8?B{gD_}<@%E9xfp?rB*$Ee8-M+c!T z_P|h~u)QM#m{xH1Kuv|6w0{AgzuSIWi(%=Vk%Q+b=(*Tw+IN&;p*!XTvS+HsMrk>& zwBBd|y|~_bER)Khc4~+kd<)*QnU!f=NbdR%rgFROhw;0Q|Kzp)>$p4<<lN`muyEqC zhqABvfE>ik;7g`=3>}KntGA@NEpl^J?vn5WF)?U@se^kmExiZ~{?aZWSG0-@ANGy- z>6sS)qo-Z5T!QNpc2()Zd)&)J){D480g%w`d<{VSnO7LdIap<9W_hkfZm^#TwugC{ zR{!t42Jgm$Q@x+P_5XFNc$BFun{$KFU%+eZ9&Gq-@BJ-NvwMl2X}pKPi@2$}L+5c( zCt2BUrQW)4sxp=-MUbvx_K!s4Ovjxmw`R2f{Tg5O%-7hRuE>nym%@eLlE`!LkAW#E zm9kFm34VEe#zG8S%Kn>+@vu&uEawp^<UdD$u1ItC+wFm|%~kVJ*BL8m=_Sf%$j4Q` zan`=#Ejo*$DoYcrjkaIfzP);NhujE7KbPx$DnuyGOBTnbxWEayTiVi54#8pViXB^Q zFA_BcWHA3De>ip`{kdF`3z#ujXC9aK(qtS!h?VM<C6%jnZgHGv<^3m+!{pTaOg7V| zr^^LT2w;cF!r~%`{g`1YZ&LsnwNKMx0m20wERXqE9*f<jKoj#K8d`@eh1$o7;e|s% zi}?-t8$9Xjm?bpqYNr67(OXJ9<qGxzaM!~WCo|t%FeGo8@jsOP!p8AvXiF~obCLyC z^#-_^Os?u>^Y4mQktQf^9N!`i3F0GbeQP-zOl`ghU9~29@nl^JD9p~KOa}C5wNYp^ z72YKaaU0s5wzga`o6Tu`f~cQ#ZYd(r7bC(SwU>@5t@`;aJ(O9=6Q%7Qkj~q%8LXxV z1$LPaX$4mrzTACXyQ;n9Q!b%$`OBbUBo>W)4S}pcps>J<kThH5T(Y#{>cIXVd$lGm zOypVktM?y7UMy_$I{v{x+~C-T@VYDjc!+0jdjY#w=lK1<E#sqn02ZR?>;3Hf@Uxgt z9lL+SBSiAAT=|2JILr|fH0eb-`lbrG#qLq$bY{ktzcjG28+BEBhi}kGzi2rx6<=ae zs*5mL?+KE8k-7NR=R~D?NPz5Vq<`J}5^}EZ{<(-c@Hc!>a8Pc!UM=S}gqKWxM#OPV z%Tv2gH_l8C-Cp`tF3xZHNa_-u`|9O7ac_IKv?yCn&LXS(kmPXy1s3_dm^`~`Q2WUL z(yHFhtK(H(5!WvqkGTHoH0xoOoe|tY5J>cJ80QQcm$q<7jP*KiDei0%h+FO3T}MEN zC#|9L`HFVm>}LCQ*s|S(+jw>Lm;||yW-;#(Z{9V4o^?0X%gc!B<MR0$?1$V<Ypec( z16=@}2W8;6F917w8lydE>6jNLX<2RGuR9)GAGiAD3?M2XHR88BmoB9Inbx*YlpNnS zK@J(iLbmBflZ0pnGw=0d-6MR!Wa~Kl=`_nd-s6D14Y__nNeKi>xcG_}h~Jz&3VQtb z)`ctAd08B<oIVUZ0V)uz0D^}Xi0!n29v=moCKljv3LLD05*q@1lyw2QN~9%PF32b* zSw6kGJWjg!%b=kQ3^CTDx30VXj-3<d!@(-7F)?*?uVh2#^xh-Pm^xuMQ{URz5yyxA zQi2bCw_?&jh{V@GzAKUTQY}WmbsbhxGI;|8kbs{j3Sz-!cODYL$qRwbVh*`Jz(;+D z3OHgHsI#?7(Gg;UdoEg(ep^|NI+PhX^aOn^cx};ZJ?C{~PuYOXU1(PNXx&nmMkl}J zXGZZ*#R}GDMN9=XUQuPAyirB>;6iO6AiIJw2j?CtP)-|%(5l0<V6$6D)sKY6m_2}k zF>0I|6sTU%u)8jN>FX}$H_O%7cQo{{4xq;)Q}jdIjYB{T#K8edP&ju%2@?K_SLZmO z0|YtrFF_BbKY|{kRY*_Ow50qhSO-jcTTv-8i(>Z;8cR)sqVUU9>hkqjgt=t?7tW0; zh3YNi{i8=#3T`&5;kUlqnv9xiH!PKzddz(J5iH|Lq;Q%H+xqC!tmFNXO|;E7REL+Z z%CQ}}RGy+P%`0$PQG$OkX9N%!%|j08<=zKU*DjcG-X0*LIJ-8Z=bI+|D#~aJciTC} zw}2r<$4(_$3#eiEo`FTqs|ab7N-GPFARFBl7Ofx@w$#X0zhq7z9YDxr!c}E%AEv4^ zvyI`45L$NYi?tE4c(j^icA;r%(Kxv39Lx`&`y+9bZEH*52k3E^)^QiTy^tz5;-wbY z1R>qji*l#a+ESzO^zDzWArp-s(TMP=Q811wR_s}|B7i|Fb7y3iH?BNq4(G_lOm<%i zogWr>#(ei#$$4f3MEiQ?gU>*ks)f%DqHE@v{E5z`p^xtPm`ie5Jn8}gD#5#QQG{>I zTJ$JWC4YNF-vn7{mQ+69d0q=!)#6oH1#91nynroh%l0ANTKxBh2~6_ikFTG8e4XR; zUl%aEM~?#qZ00jRT6}218is2(^6{A}`NJAZs;%w@QI`-|E-C_8mEd6cs80;$YqZd! z3aT}@3T1t%31)>#hkMoaw^|!&gr9y({TL#g)ryV}UuYp#wi2z~j0a;ITSl$D26xk) z93O6bLk05UwHIv)8y{&<<OXM2oVcG12>NYSkC!&stY7L5sdaplX)WO008csgek_DV z#T9p32E>oiB>x8#@gLN%k6rao0GQ&ezYe5aW`F>R<kT&u|7ZJF*<UVv_cmkLSCX16 zbbNKLZdV*&!hW+LOLxCPAWj)O9A^(h+$=_xxz22R39p9^qBGs}g`S#sKy}&3KXxB} zY46a6C&R|aux(c&?_+EMX!PwOGr=AYa?O0j*Pit2pFK8EJeENLzv${5vFW$xywXr7 zE_Dkp2dSE4Av6nsu$6txe1WZ)7J|bEsH6{CFpG&EL3IPfwC8>8GQ%=Fxr$s4CIHVU ze%;P9I#u{(tSPAuHSaH%MlW9Bsh<4y*3{J03q6?Hl^kD(At2^j+;+0#Jjv^}^M$9B zDMqGX`_BnDd4Zlit?Y387yIwveYIw!ou`yp8li}{W}2->EyJGp;zW?!l=9Zq9Nt?N zYaVYZSMYUi8fOv)(|XA74|>+*(<*kxxVIRv?qud>uG*E_Hu&Y@jNIH{uIa1p1;Zl^ zKdNk`SJXW*&-s(t3ZL#3N^$Gym8q)|z1RJ*ROy*h00dXpnt3o{27}whS#QZV$sZgg z7o9*S755W0tO^iqilZe(Ooj7|LA7?JaVNfO*l;t|DmO|JdvOlkze*;l7<x1xU!kpm z?4A)p5xWSl0}&*1Glb2QQjmWyZ@qOfB`nYA5+>UMIXwWsq?NGjS4q-3(y?Nrcrmy> z6-FgN5Dx-;UHC2|3o_D-awkU*4#G|oGERL=%8^bi$0wLJ>6iC}%dpk#c1Vl>-O%J; z^WyWUWk_CsK;YhH?m+T)5=6yR>pCSl^QC>RS}F?Mz|-JcgMc&(ol@mgXSZ<m&?i(v z=}oW!OF&UPNPyKNW~D*-rLSUbVXwI^z@dPE=+&3%-kfQyjvZG8ch!efV_Y0g;!dH# zq?|_rZjXv9lgg^|cOL9Vbvj8_gh(bT#OLBLk|0@(QeoDNF?wk?1X{ DG^vr~!O< zB-6cgOHP`D<k08r7vIwoKWvUF)onA8M$${hY_g?&?wIab-`#cy`n*v3E)|_pM&wr> zM<y+rB_(m9KgWvaXARH6*B#<qUQ|1ke|*T+Kgq^g8QAF&o=+qY@eLirNRPo!+=b;| zSM@RZin&Efl2?QOWSE;UU_6ZF7aVCk{t6rF+NS~lRd*)xZs^pYt*#eRx?OjAQ&nDH z^x+ghjsGEQ^|D@Rs-MloF)8Q^Fse!c{`}oISGKXheNELBsPnF5#UrIuVO95POOusW zE@qR-EnC4rfo|QnV(Do#7wV#(3_XJ!h_PCzK3QXT5E*K%b9T=w;PWogN3w(nq~!z* z=JxugRh>t_n-=co5<?3zg4LWHtfvVzA9RfJs0Cebe+<<I?3~v+2LD-+H+!k~7;lGz zoKeUPhc;;LievK?yVuiOHzrpH(U&H57fCW_@-mYKLy~tpX9p1dI)d0Z^N?~f0%Ibt zNLRwU+;p|+t2OOU@rD!T(;}j-(FUZ{J&k3wgoTCqsSiW6Hbay|MRy3IqAV|;mSXOu zz1%LW*u{xF+*aS)Jca+w5{=d=_VFzJr52KkyT8xGE$Xl8q5jEU5qd@BAE)i9>0`{x zpALhfFb@mrY?k(*S{{Wn2;JMs1Koyhh5lX;OL=%IA?9h<_mI#Ad*z9ol#Pd^8g1$M zw04BKHUbqG1_=H(i@3yi<!|V(Jy)HPV$@vTk6}W8Y-5PvABnpU?0P@`2%*w>lB|P` zpt!DvifY46mFp3D?>8QwKK5cFIBKC1l0bvvB&9*3H|8XArbGUEU3#CuWG?gQBk0CT zRRxhTVsvU$=dx1H%a-7QhcU)^#chJoxn6udS4I|}Pkl8-g*$t)mz_O6!5x9S-+qYi z<UL-HWcQy~X#(zX*C9ULUA;ulm%r|%0sifGI)q2{;*yCWB-ii##mIr!+IJgZ)oPPD zuebMjPbiDAy#GVE4fM#_Pk-<-|2ppcG40T}8-5@$L4l(;AE~I0IJIS-gp=PATFo@( z`Y=H?tSlYi_lYnmiNwb*j)I?>ZKE6D-&-5`E1vE<$hgYZMa;p8DsANmMg%#ofV!KG z7Oe3?AI_l0Zky?=g(Rnr-4X;87Xh)w0&eoP>T1WJL#D5Q$X+WXkbXDSyZ<bre9IV6 zcoIOq{U9e>u8KQZ;#+^2u;s|QtErdM-q!Y;<x^XOW_Ef><hLH~hkfBc+T%ku9s+QI zysZGtiJ0|!0>P}Xu-$jpF@1Y=P0w0)ul$ed$N#SWzt#UAJ|27(y8n-FSKRv_7uU=F zj}oU<`ZVjo%W?WI35CALo-Ay-Tg){8qN0ykVIFYu%zyZgdXM>ToqyN=tGfL^tNZw4 z{#k%)vW#Tk)d{lv{<=vIRDBD;(oGUS9@`hGCvKbA=ZXAhH3X&bj{=OI-M-xgo%#K7 z`{oDxyhkS<57>v7o)uXKkeySC;jzO1SsRaU{6_(w0{_@`QJ1-J&%fKUdSXpyf^02T zSUohkm$7K)!O=I{(+!4aY*)B5(ed;EznRNDfnW!;?f3Yeb~~FX?1vwi7ha?p^FAQe z%ulxkQ@64tK-dz^DtB(rAdoc9rM?yG<IF4YZC^B3p+!sxtjm8w5;rcD-X27oT9t57 zw#ME3k}C%7xH`I8(R~QML!w1u{DjwSh|U348Mg)<R6jD1Uf}M(0QIPV-SEnb()so^ zLfO${4~x1u`FB<T1~4`bU3|rIF;ix|N9Iym<4<*G>62Edtn9C4(Tsy<EC<2!r*c<< zI6@c=VD{x4TfH>-9>%s}pEYlMgTrr@qlP@0VR)y+<KOL#HLm&7ERl15Lw1bPI(o;9 zc!h<@ir1cI)5Gqzn|BSqDHP4gi|;oDw*PHvR&d1w#wF%{JjcJ0_y4vzMG8y3ElY9} z%~k}!+5~iAjP01!Ny{Opr6{o%Y+f4W*dFgF+B?Q1n?=Q%M%j68A*1p;<_Meqa(?x$ zcF7zbX@fy|o_4;?S6RbK;jwf}sMgg@*>n}XrZrrNLXtXnM-{(PBhuEiW{ZHnbRP4n z!CL&vCf2_{x5ymjtfCfrPS-AT&iF~DZ5Yl76_zs?#J30*9-BmYBz9`Xg(kwa_3Na# zYzTzt&8{eV7<UEvipJNMc{*3;D!y+%{I#i~yszAr=i#=M7aK<JAHi8MM+2%lZ=j3w zBl-fvt|GVk;bL|7JK_ip9O{JP510n^Gi%Khv)I9OtNOH&x<UqiffT^+yB1_r<H=z# zBvb2Pk?Yc@M<B>^>k(uSoLBN~uqD>F@2sami=uns0mt`6OL!mtiBz+mj{G}-{j#5z z_h0Y#ui&`2z(`dyH`5=LWe4*ffyGsmKSf#+;+@?QSAe}^(*Tn((Uw{Xs8c$=gt=$E zt1#$s!qckH^bU*N3whn-^NGIq#5#BX-MD&R!kTJN0wJw2g`cUGuFS<zbvGZy5N?-1 ze%q9mzTAaqcFxCHd~cy3pA_mQ1ZQ4E8BP$t7dTzz6Wt#IQ=Q<vOOXHuB;q>2=XUgA z_qWo9k*XIqwz!UkL&A#3Dzx>A2Lii=3gvg}))xf4%08Rh5XJ@6CSJ7&<Ofq4WvKM# zhmX-)XK-OK%opqt-1kI~(4mVWIQArvC?@PFV4inuTzu?!HuCXxM<CzjkKufX5A;}A zBvhsR#iPxt{p#cZv%IVsXs;Lf0nz@JEq~qulYe&Cpd`5j(VR<g(u8^|!P~yI&iHKy z5Nc|srAIvqhsQ@q9=jfF1UGp(>k2OVcMI!TU)ghJblv3W6-NZ->fYNWf7{oXuQc=3 zApVdAiMdto{H#2^<-M8Gg7SWf+j16D4dI6T0`n1VePP_Q5i&x;a(T~mKQez9NGSJ| zOAlU8CK$Q}<j0SYec)|m^SZK{=bxfxluesOB`t3<?8vGu&l+M1rWPIC`H%GJ;ey&% zus<yBhgd)R6r*6iZSYa=BYo|xy9ZptH#)-7!pRKwjmqr8IVyqlKmj<S5=c;dGXzl+ z>=hWu{7^G;6B#8t#8d$SI1f{#xV4KHz#+KH4eVSvc@9<~#s9{aaGDrCizsh{dILNF zRGiQ>pi8OUeMkfoM{(l1)SNj$sqE`LMLXk&)z#Iwa7mRXiQn}8>($%t8(c<pc~Ck@ zW~lVOye<DP<B9Bk2Vzg+05^|9oyD*fKZZ(C%d_9@psl9!`qYyA5M-3J^nDmSH@(yB zLS6MbDi}RFG`IbYX6qWbv41&OVVK6)zBhR2>i(#ge*tF7!jOc0VkvK76tFb3Dyh5I z9#`xBE((WKkP9kKKHa`QdvGhKdRjnz)U>9_YZ@yQc7~bt-N<<OSJwl5eCepE!>ysQ z0=(DCn_!(?fMDv)HaL)&d5B;!O1RgKapO!sNKzRfyAA>>)yn(b{L~aWaQ6BbW49h) z@G6FpfdM>XXHQ2)9)$!-;bn+CDRI${=$P3VzoyS&RH6DoE@noG+eOsDNPxlG7EkFq zH);%)2ZV?WLq_DApCg;TPfykgyT>Ma3`Y-54WZw`Bx5c-Xb9h((s{GVomtTzSb!dU zj$(LfspI@MezWl8VcyU!r%$PC6Me&bThpa6{Flb<t$a(peq3oNg4&fP1P0do)3#;e zYaWDp+C@4F9PQ4PaTy4xUHcgf9Ik<Lk>sS3qmTZnbC(^^yc3awg&f*n<HA=X$>?2W zoajzd>bt7Xr1@QhmgHt=TtgMjqk>i-2Y8kqCVs%Sg%KJSx6IzZcXg6f?UqdO%;P%F z6<H$6d-RS0?yLA=jYG<FxR>t$7un3|GP@__gv5D28`Bo<F_>PmKq^%_BMB$3o<Fz) z^#FX*JN@wlP2-~O7d#n?%jLWNNkJO3W#k82iUZ1Sv*h-JjTPv9N?Y+H=eWI8V`@$! z(LF@f+7GMayFl(ZR61e8;$g`6I=0_bJZ6L~e2Vz2bN9|@|1vmJxY3)+4)#MP%J_Wu z-7LaH3zr<=Y*j#VAXQw93g{B(5w1)HbQ$D|8&LsCgR*c(RDpNyIC)jT$Jc~|s)8gy zrno>=kTPfoM^pvLf|$53svsfI7H(4&Bn8x6xCW9_d|zLzi&xf3#6;NB6VeJV&#Q9B zRpn+)aTX+K7?GOq-rbZ(SkF=?EFAK<NbM#p7+m-@kQiIq$ZRb~H$b~Wm429mBVPl7 zF#wzK9O8Rfvva#^x(6Y~d&~H)_40_9sdcgZ>4bzr|EQ=D^_=LEgyYEVyQS#242x2A zFk+e2FOsGnCwYxL1wc7m>w|cZ0-lb2b(_mV(P{g+C>hSNv<0}LK3vF%@QL2icv6CY zs6KTJKNPm#Ic*spkhM2?EpSRk-*p%FKn*0$O36pzqSQcFFoZa?_d(W743^~9LaWK7 zrHfQ6@5+BT{nIv~s*WVRQr04qs#&Q>*^a(O=OUD=G09Qex+^RPQ7i`-CLmMYSh<W4 z;Xa?vnR|KD*~%?zF~EtMYkK3-`3fcOfxIuI2$hBvGn+OBBm{vhUf|0M-zL$6gAnst z8B=q*qZ>Fib<ibN=l3YweRa@P(05$2I?zRI46uHL|IW+lpHbZu=*<N3oWr29tl3;$ z9X50U@Dxsz6)&sQ&}H~Tk{Q<q))jZ<8r{dadLa9>oO;>gO^>`m0FlfecP2nmdiGyH z`9H5d9b)NKtKq*X7nWO!RJW#t6?+c&!dt!Hq<*HuOp0GKFNPqv%JtSU=#ko06iYDc zKdDR)Z0FgfwfDUzOUd;zt$HcW6+5qMjL{{v{k}9?-uU`takD5cV+20#0YPt0EWT6p z+vKTZ=j-`CMszL;#0{XSGRfw_zEhVF1g9nW^HH;_y>4c|v^aQ*G+=j$V+<C059dLV z7VED&y=@qu6ccFki-CnVP0?1{JxU*zxkbjM!b`%xg}bb|{5se>03v0dA|pH1egFZo zexaf-<BIirv_=H-$#0fokKQU&hgg*C7C2u&kCADa$-$EfPgZ17ce^J5X|hoduAEi= z2!IlHsnN-vK-4@~Rn0J#%;?1_=*W!WdrE`!Fk<l1AfM8Pa?4~Xwfq_GQRqmTz1xuY zBxmmn_thLPDLsm7^OYo@YB>X{mB&&B+*-1Jd0=f1+M+VB#F!=CSwK5^n68wAqm7fP zBs%zbt4%FG@tLQ0s<{bSz4+z!m8$;sqbuXZ#W^0$dETB$J(<=EYVmnO0*H_uSics= z=ipqr?4xm>>N*{-2X!rZO9yl1TfS*)c3D2M4HO&vKms&ZZLF5zl{*Gj>^uAN&CN{Y zojfkymUORR>4OxT@h|pw1_(VQ)Pb9nYG!fAJC%t$3(pd*zF&c|Y*^F;BByR>#>U*f zJxRpD^p$eWs0F_F`*Sz8*-%cwPGl(N$dw>v%QHgTX#*B|qHe^jxB4R)J!M+w>cD=l zi~EqI(7Wvr6AAMXl25Z|tLK^gVmfaB!uATHGi~8Ax~RazrIh062^{4%0KrGX79p!D z&n2+G$Wy2?uHV1UP4eR>TaPD(7tv|M=OBm>!33G)Wqq02+KgH%*?cJ6xE7Pi`xz&F zJ2p4VZkcR8(l((GR6C*{S845GL<TZul2*{SiFeK@8fCZ*AjEUzgL+17-ge!OT-r_B zXw4)%dHQ7WNL@nOwKw@r*jHsFh@Pk-X<@m!bQ0d@lWJ^w3pJwB_d!s~u*Tx`o23!# z(rauYo2N!%pBH3965ow%LE~;}gM<%zHma%N9JN7Lq=R8k?0;E%RaZM07OrI$<d+m! zneL<wfiIPhTf~Y9I$Kvx?^HZ*>}I6l8npp@S7JYIQ5z&DpIYRv!iuVXLP%hcDknXB zb6i}Y-UaxCG=hqgWnv{3pl3oLAb~J01e_1f#OdjPz`)`gsskYVKi`h=tlAJnK0yf< z7l#LmRuqi{ECnUXuM9SDS&5T;MYj~{x@;82%<b!(4la0D?4CKgNb;{NYX^iCJx-i= z9e{X=S5oN<Gf5q*av5?<wz;(85|#b4uSO{^oK_lSJh==y5~R2-#C#+iAi;B|Eq3ec zglVwlgLrzKv`0N=K;@+djYexVyd>f!8``HwvQW*8(=X)0E_Nrk!}Lu``Og(;I`y0H zHWJgrGlgO5F9jEp1B@&KsYT*m1%W?#i#ipQckaO5sPdeKRf%^WS-H9O;9uC7evIg; zhBA!<;>xQ%7cN#<j$gJQq)Kb3Sy+Gx_$ZmQ8mheatR%3D4cbZ(!`7_>RKBgMWY?lP z9l3n_tw|Oq?x!GCbtTnJ#cXEV#{7Q#oY79Xny9&$`3gv&@2}biPyr?2s?l3~LiO|I zN+^f}ta!L<NIa^Kq`TfAE2&~uHMDewzO=84Xn`zLMlg_cC*3we&;Z?_)g0mCCQ6{x zq7g{Um?%M%j5z5~V1c#$KK-pA%Pp{NjOhHM>d_V{hF-G=CEm+8pMz9J!C>aan#HvR z3R<!~ij3@{BRse)D|z=4_=kpxA!phi<;cVoRBzAXt1SV2=MhJi*ST`>x$y8)e&?f- z_nUWPp%hBG3-Q{WajPxi9cvew<#IDIo`PAg>`gOzBs`*aA-If$FrU){xB;EG;FpPs zj+CGiA*^Ju`^f0+`D?Rv2x=lQWF1q{g=R6`;_ktUR6?p<RszE|BP*f1EFD_39<b;| z;+KwQQ^TVeq?%F04{bi@c-}ZaSdU$|qF^Y=MOrnP3m+|3IDV}ysrJ!>!jxsqTza7e zH%}ZN_~Fn^EV6pQHUQaR9Gvj+zKH6qonwxql%L90OwzewiMq7W8XVi|7gKQ2N_x6~ zpFqPKsTFe{ctHS#RMS=y<Rp879v3f=WjU)KupwQms@yzc|8)F$f6y+Q{w+XDmf&$m zylQ)K1wPPXux^uEu3Br|2baQh#XN7`%H_-Np^=EzwRH``SIQZ&nAH6JjyVy|lo=r} z6Z(vo09R^DW>2`MvUzP9dhfECx9R<#hQ2>lX?><2A?`hbk&%bA&ou5oVd_k$^)hY3 zX1*@2ev!{RkSaMSXsv7d&RB7_eX&KbCy6xh<^4nJZu>pvpJI1&ks|nf&|}sf^!^ct zF|6kPRlL3Vh$^)s*)3mBCzY7Q#5ZV9^^MvRXaa~^R$jDo7Io4KK$2X5w86ViuTAb3 zKl`wJJg>X8Gg}~S$ISxXfQ&6x&`ddBR%gAO9RoTY-TfEuUI^x1`EQn_mV;!a6F+c< z;)fN*owcX-4vy7hk}B3r@Sz@Zc}sQLCl*S&QcLi`TKoAKok=DlxNPymA{f3Ysp?5L z<`%#slx_KOA31D)ax`?%KS)E=$|i%^)0@Xn{QgcWj6D#exG^~Mp)w<2a_eMnFD$?q zK_yn?7WsmIcFM8lVDZEZNlh$u3c-0z*DeMp8-Ban*k!PCP-uKaN}17EyfhcA?#e6{ z#pqGz8K^*PZ*Y-<$&9q-YvgiG^b6&wzyV}>bo0aJ`O!)8TshiKZ$n2B9niAQu;7xL zvaq4OP}NNIjTM`(N?<%}37L_5Cs3n@YlZ-))Q`A%2yh(R=l5Ozk#c3PBro>u55JGW zDG8L$V7@A!jN01Vs%N!MRel+T32oaR?jAY(ik?3bCQG@8=a;Qjm<_kYNghIAEyqMi znJ=MXMX$YwtgDv`=Hx{nd>u}`+V-1?n5-Z9kRCwtC;BsXi!v|gM+byyL1J|+A>V?< z^1?a1vl1}!TuL#|tjz-!Pi}~raZcE(pfRUsir)^rOIQm`bhFrG4xP=fkTpAp(z1D* zKN`K#zqBXeq4v^K<7eeyW?PJ#Cx2}`s)G^DyFB4?n_&qA>NqP629-N)V!po%PCb6$ zw#GwpRhfzF*r4|$<PuUk;OLSBUbPTIj>;>$a<dq7GvcB&edB~%Wsb$QmISCayBg-c zL&wmuz8T}ibZN59=bStF=lM;0<fRoKL7f>*`pf$3Z;xVOI|czqs$n#JvWl~@Z3+pw zzQoM`S$MPQ2X>ivuyYCWO?M+*s_qxWRfsTpQtNXGKCC}_P0-loNjyBOp})PS2VwtY z!VW|B{dN93!W=bp>u!0?Gv>{ZTV(j%o!2APiE$ErWRGQek8G`$R&6Ui6tu3(+K{}1 zc5jT4FbCx{X<iMQ$@475J$SQ|9I~fEpDh-%^m9m-t_=a(thXyXqSV71M|c7QM@+jZ z2-1<dsPesHA?tV%uTYb@cyV)^`}}x-S}6v>Q)I4NSmqL<dh*UB-#EBB`qQ+Cq^52Q z_gY<3&b*rMtWxZn0#V-L`~Lo>o-SV=&Ojf)d#74Qge$sR{|vzQ4W%DiScXD5nPNqT z^>?LnJ=OM9rN!cxdl1o?<1ouJt>?k@0?`Dl+RCQ2rjFmdBQwjmrbwU0D7GD)K5N)5 zrAs}r?i=lC_;9&8^xl_d<(a!{`&N0?I(L@8)!*HV9wrh<m1g;5-)^hh?L;wM7nk`w z46aF#nsvOWr9TVCBxvk=6a@xSjUl&h$)wN92M_39MUoxw=t@hr*r=vUJvT>~INcem zc?yl)5i<Q5_wvNvcc=azH`=&J2Z?4wiau~dvom*MtW5RO6~<#X7t7w=Q;ryA@HLK7 zutN1df#JqSQYtD!aMj7Eug`ZNsRCmBu!QPqJqCw2p@D5I`tDp*;w>?IL9D)Fw@qGr zaBl8yY^R*w=<V5`=Xw=v)AoA&qH3;MP4O6_25Py?$B;>mS>8tIEsE8;C$&RcgEJ z11%h9>7k3Pg;v@)*bHFc(vJo(#>WTT^cYn+T)YhbmyQiD1TqG}c3VJ>DwROyIJ>jy z0WSm#6gX!M1lH%hfmq#%E>{~=wwXzfx8!Ty+Ktn)+T(f$nDhe}i<up#Z<_mwo^v}# z(s!wE?qv^(@)Zc)+OY@}d9cyY?n&a78LVm;E(id33=Xxk3CUpq;keLKSBK=|0Wr95 z4rwv-KmEkwS;oEjJDRhTc$}`0>&+MB)Q-^1Py|=o+oM{;C9{O*_>9#j24f^(_aWI? z&Xh{$N*85Kqp41187gQLwh~`C8Q@%$<6zO4*8ger2<O;fZM?B^9Mlx~%|@}b&3j4V zI@>!j#J1&+SlOi_F8<;@j00s-d9e8BBk4D9>huNjh}bCHrcxV2U#)mS#s+I6N^s)v z`GJ!$0?C8ea7ZJNIEJPZ6;k`r?62vkRQ57~oa*?oH*W%ljBcCvkKHpMNTD3wso4wE zBD#WdH`N5&)wl#RVKA6Z)z`D^(_80$Jsd#zwBH}%x4*r@Rtj;oCb1paY~>1W9+|s6 zWH=X^w3J;IU#_SI&$R9yTe`S^WL)90*+&og?)mPCf!KzIfPGAzMrI8315|(vzxfSC z%r~QzV@u||uBMnsGX%PdAG2kH@&zt3Jf^k+H%Rq2CjD1(`;v#EQ~Ey-+Jx}A+R9{L zu?a@)KlSFMb47`293f+wv(V|zxc$s2VRS&EC46i(EoJdoCVZG=igk+1PDLhmZPf}0 zaCQ4xPt3_!V%VCc-v~&s8msV73(EI8Y6i!N1L``kdGy9?PErb^F7OWA^v*AFZ?$D} zKj96FJf$u*1m|T!D(yh?+Hfg;dbj(`vw^hD7N=3;7}=4yNvIw3F8ONTn4|w{+_l1Q z$)#8he7P?n_de__>z;&u<d^+PTKyI!kZr6#a4(t5z6ho(J20@ttw)UzlL$5LiEmie zoy8)qFy@Qbgy&gJt0j$4(PZ`Ys@wuvkQM3n&Pz92A;GvJXR%;;M+n2@A;Ywd?`#TN z53$4;QRHO?WBj~w+j^rNQ~CJHIkyO$t-_H@7|8X63nChK>>kk40vGYlSbHIIbnp;` zN3c5%99*>j`36CwI)FZAx9Tw(VS221g+Ii-^6CRyYNK^VnfVy=8?#eRx%fLgnakN1 z^Nha2NOanqL2quk`nRgYj%r=>pjDyF*QH!@82Sn^6O7LM;Ozz`*`5o2^wBl{Mbg+s znY-ZRR%`H{H+n_AZpSiKy^u^Gm}rIi4J%1(*pH;EB)<A^8FQ?Qt`0Xrhp^e<2GzqS zLn<xX%FI6x(aIk>p|;iHbaer8&!RQ!m2l|zK-uTfRW5pl#tK)roM{d*dE-o<Y7}l> zMlcRxo|`<ZtFQ2;7GYE9IDYlqnoIRyci$Az*Wb23Goh-~%%az{XDmO!TdxA%0c5ti z&IFCIbly_L=$s6mo~qSU@hGgS?^+=9m|nfPJ8V;Hg%UWYWLdi1>wsHHy>||Bh{f^H z;Xe^d&B56Zau%b^y1N_}RwV&_Wt>~atbIE9>(|pR&qaMV`55X8zPnlKn_I}mqLV3j zu~TpiJkRqHk56gT-5!<c!xWtFI?DF-;6&xY;8;};rmS-=J%r$ed{98%HVp-Ph4ZW! zMffUR{5<37&}rwkT`u-Ls>4AXKnkwhJpQs}{nu$FvGs_f?7%CXo5%aIxNH1|GpDLo z&g71H@Zw^gOH|eL-BKE&-{?H4qMG;UK|DZEzG6dgDaAmvBWi|)N{6$g0@Z~m{R_@c zBjsN)%#TLY=s3CqHX~-;rHOYA;IPYT_~a4zbkvly${Z&x#O}DIBGB4wO+tsxy453# zIq*AIWim)qaAS=3Ks!WEw<=8%!6a#sN;jeE4Q`gyJzdL;u3i;?eB{PAhxnINur|L2 z@4}M|cbEEErYep~8=Z=aATn#VtV6F=V(KOX!>5WwU${!8qax6YP6@bP7k6wX^2-RJ zTy!(mW=1H+vgw$m*@wD%p4Rb~=AIiZW~SUL*Y~s_o64z4jeeXD+|?XOCI+Pj*4e`J z#mPi>W$rJo9%WA!pt-W)b5$GmmwFm{iqmp7xD+7zTD{3DK{i36Q#{g9$u^nqtw*|# z)tPTnJpvwV=~kfzZCnLMii;<Y+=9V`hnhF*+iNZPAT~~%r-M%ZQ2W-N7aM|^2m)Jg zh|mN+w6(2R2WT~K7;-g^yJblt7srjZ)CT<V_&HTVp(cAgPVhG93TPT<dK-kqw4C0M zTxf+J_OD!nLrN`fwzW+J)vh#4aLW4<YQqQw;<RtFnBAWNz8Igl{!8@lR7Xj%KLZay zMCVi`?k)g$-d^b<@j&EUy>|Xz%%WUHdoKRuH24c_h`-;CEjSGwik=xkJjkrsoHf7L z5R-&cp5YqL(3Dv%{EcDx4mcnuZG&9gCrhe=t2)?hx%2z`WN~s@P*H|hY5sgUWWvdA zP<wU?VZ&gkHhEMVd#OG(rY&lf(>KR;or~v3(xe{M+D^AqyyqvfWP`;LTiLVAcGXSa z4P%eJnTx4NH(MZjlsgjwmM<1!U-nj~5);{0bdGRGZkU=aW4t?wOqCGt*%qcV1rk>= zafPtbQ}=YEIxnNaZa`NT;Y(S3%DZfu2_ghr#&+MR;;ECXp0U4G;i(V+HA1mj8_Yoq zN);f~+GH7kF{EC|Ld`Naz_<Ex1g#&@Kh)o1dkfB`3x;^4@Z|<;B`9{J)RN7j|2#6e z&a=L~e(b{Wd%TB3wQ%G+hs0U;Bwylw?E)t>Y}NmQpH}^!;irj$-U~r=p8d$rK@A=- zE3JdN5BgeEWSfjj*sVO>;7`$!_@z0iXG&_MFjJSn>%7M=#-IKvkXfT5S3J6Wqh=(Y zIU1P#CZ)<bcdB0o-iK|P(xMJZQa(ZNU>-pQ8pm`OgyM>-Mo}IcyDu$Kd>W_D;3}&t z$u%A0Nzk2Z@!{v==3m>0C0}{)#s3(u^{1$SvF_;@lpOd(w{TjYw-Rzbt)T<lY(g-I znjB`tWS_pb;$2M%dXz_))(M>Y+0Zd|yuYkdAwR{~VP87<q`&>(NIa2ipGxfW#gGuT zFEraDuk}~T7>$~`U0z~58x32_OxWBA3k35ZV{;_UUU>dy`HLL>Bi$q`)Bc>m4qWM% zn$r;btncm+2hY2#51Thvn>ruq=(!sT7OkgS?u*)AW&QEIcgVRuq@zT{QGV}Nb>h0y zs>Iu{SM{quCZK)k=Wgwq5)a}PUhiVU{?%^jf3>^6mH0*blkL`ykK}xJ9Lqr>>NiWi z@vP83(5`aq;kiCsWNyTAqxGn@AZI&*Q#v3q-`!8|o4+qu=-kw@uPCL1&c$(iW$*vJ zvh&ASj3EWnf9dDRgSo{DoBeNt?BzUR2i6_CN?VwJTrKM2z63ygWPs}mzg@e#H01RN z{B-I0BqrrYc&zr1kYmHA8Ub1)@qNCs_agls_UlohElvNk-7mYdluKC6MX#E&iF94R z;EHjLpPrSe-!0+Pw!Efgf%A~|XmI2^VpQ16RS4-OgEc$^j9WJc@L;Y>xO(k^N*I$( z$FHzv)bol`VFEb~k{~th>r{$P1RO0}*-GVc2~&nzN5W*m$`r*qzMRy^Z3edE1-RQT z4wXxg29i*~^{&?NmSbm6wJgqIm|ivUbUS4YM}#@WMr@tAjGt3jMA5ByQ|Ac-Cbd2A z&o%bZS)~fOw)qIcj1(!UY{;{>aTJ{OdeXxoWIg*P`rO`cmeeZoLmF@mu?uJU{-B^g z9X@IR;HD%0_4N1(%cqFz_W<0^zfsijkMDtAy}!b7_W0>+0bE_wAz{hdX0|OWW;oyG zkKZgc0f-ItxT>xWd8MH;tJ^z|#!^EN95C;wuf8}V?L&gnOMw8saAIkh{?w=P8ReW+ z>3};SFLX`zj%*mbAGIY93+A)f@2U@<Bjds>KxWDTDA^QL$qv0R+I{Qi`*uvhnP+t( z2A$t4H_l;VL=<+}%XR0+EM5>*8fB}c&lfG~>iqQ#_uB#_14K1sEP;4b9?r@VSO^q@ z5+<YA2UH`YZ7+qFF8PzEH!YAWK@YCUJi>J`c|ItLjh`c|>-Eeje7HGBBR74-bst=6 z${EGH5J}LfE}OT#$YZgvtJ}Vk+I^7N1ie>wXQsQPQqiG4Av5YAuSU1lW-yziav)1q zmbyicwm%#W{+m`2V)hN9#-h{#^2gn09=@1(@`fbls&3H#4`q_@^pth}NRJsj=31ek z%+2IS{eC|aZ#O?|gFl-8#m0yyhk80V`lDtEVxzH?Ft;T0-nM9r#<kud;X4N+7EM<3 z9$u({`Y=`zF|x(@RRc;25V;AkWF6U9hhc1;9a?-b-(G4Kc6#aag@jZgu`2D55l`#X zS_+?s_AA=u!~w`Rb1J<nvLye36BqSYEr(xcT+y>PQ+|(XUXotG*Gfseou`~Ubh*E{ zp>Qpz=g6gsUMBxaaC@%9<!G$FDAD#gL+MP(fz_C1{+QQ9c8B(!4Xpoa86>}Qbb$`@ z8z08NtNRxxGDGUtZ-rb_?;jn{tLdgCk3klVv)&9MOC2T2sny58j#ZQ){1{ankg;l0 z6#n)-!H`orGWuSr#LFWwrCW7B-|;hhJMvYgcfjECG_#V@`WDe2qTx(+j4rdbx#0oS zkYnCU;zwy$wa7airkmDru<cX0Ta@^B{T9qA={#W7iDj5WU5^g&>lW$E_|fe<WHBSf zY~w6P3mzfDbA@~kEM0*Jv|E=;D;s(A(Pa41<)D}VaDb2rWhpJKkh}9d_GgHfzTzi* z@shdD8*vcX2KoJG1@3@<>uULkhx91+Xj0vnT=MzB;D-dM#R~-qckKmZAE*&~+EnIF z(UoHQ5NuKtT5x@`W^xDzY~`uESw6igUH6XG7_*&L;I5U}7iNNnpV~usJ;~X3;1yrP zJwgKEQ7{gN1YJ7pxx?a$dyfPuVj|8KjgUsBLY!;NC&oMbw~$mJe4||v-QtrNGCBra zwIE|FNPZA1+#Tze6<ti-DvK_j$fo<Pb)(ZrelQsPB(Q&ij{Pi+7Wz5bsICwKZ<%Am zMQ7T|P@Z!a4yf{nIGHGKhInWQ4{2LTV53{jGZBhIvU<|F4g9Ob!XS);k+*G|^JYs^ z1>%cx&kFPg-hZ$l>^!ZnXWmNG)MT!$U0RK(o-1)>!mLKW%rabVFvc`XwbhN%4^z@7 zPK>w;@T+xB5U>#H9<&on9t|o)YEbIZsxlE=5Ae^+6%iV^<WG3eul3JCL2DO3YX4fZ z&BGIQ;MR7-7W17)FoU^qM&|FJ!{P}Y0(Oyeig9s{Hia+|ZWxao;N+Hd(2V2ic7WmQ zG5E}9P_C>pMEHT}cJ$>p*tZ`9#IQgr4{v1Tz=o)#_hX&x^ufq1y?S3!0C@G*RA!&> ztnHVH$I5G3I<3r_(Evjks6;PRTkL}$Hdslcrc-*Y1@B<AB@LL4bM9XLeWGA(o<0Nz zqQ;(SwWE0!adS8LwL_W|2XgxxN{tM?le=!ta^SrroG}$+x8{&vFja4W_6S1ty)_>r z2me^aub5R2Gr2FJrE=m5UT5hOUq_Es=hY^r@-8_wNz{p21u=Z+L3uC<3=^In<W)6F zQYPh4FxCtB;#B%u+Q%<nvN@5FFB}gkyF63@qSc<bBu`_DvDG6qPxP`i*o>lT?Qt0F z+bvM4q>xU$v<@R%mhG6;VmWKgCmax)ZsDkh`_rs&{e=$&vivS4`jSr?UF{Jv@LxbQ zZSOiygGQ8Oczydsll_|PdA4H>Jdr%W+bYbg!gdN(;x`L^<UI?j`rTHj(C(_R#@nXp z8+rioo_ND-I^ca7dbl)!0Pq+{fF^C{=-}C-#e1Eqij(BEzDDDNOKq7=2kzlNv;tR$ z9?ncMfOh+*Fu(dLTUh8`4+o1ei|@RAxSOU?_|e}iv&Ivbf`AhDwJS~2Xy<AmU!_aH z<T`)F>WO6F`-)qof3w8Ygf<=Y*nL-54X<zGIe+dq%gY+#E~{H4t3`Ow@^hdW{9{Jb z%nIjkmYeLEt9j$QtLHRt86RkWHQv37W&sf<HQK#0cUOLS4K&Rx4*zDkg3%~N3+=9~ zuDSySb=lo2ukZ1be%4mGO=8F^+TVeX))1~SH8TZRjiGY3E!Lh-?>&!8uyPsV@^Ijd zO7=^(iHwX)MX2DnM|us012&WDT{8^@e~fq~CET`bJ-zyz1~<qkZ|`Z<2Mf3QA&~eQ z-ziw|V@%d);{eK2B4`Ufa_!#dy7W6%)^=~LX9>j#1xtA@pQrIa9(m@Y2g@88lMr7~ zKi@)cFYewQaW|EAz45JEAryvgU~Z+=q71%dH#pZQ80D63w&8_F%y%kKN(@Gm%x6Oo zFib%(kC(s7mxYnAOVw#+^dk55CQ{#Mar)^;Yrc8#e%~1)P9DJ|6hZ$X<G|1P??5@` z??`!6&F$Tcb@nx59ig_nTDdKI1qy$%OuG(bf3svyVg`u^wIca{5;KI*$<&BmPeT2; zbgHTTEmkH<B6z9{(nic+Y>JAOj%l2HxX;+Vjn1ajXKz}a3UFR%hTA48ExGD3naL+K zrHo`|`!)N05G@3<4q{wq2rzp*n_hiFdYQX5IJiac7bpzy#Q0N-g%Dh~oTI3j3te<` z>cgU7{-Bl~KpVUJ?XKr!E%Y<F;e)Zd&4bcPeb^mB#V7pBUCxXRhQ&;S&r{OdV2Zy) z#`LiumGZ9nN;&235}Go->=V_hFzV7WB9^LF(vnB@w~-O(vwqfRjalSOEM#5-i;u#3 z93p>umhX>OYgt?V2=Fjd^<OF;DpKbXJ5DW+F0!H$@{`2#1XZ&F7Uj)%15}^IBK*gR z40g8D6L`{qe7%MdeDWRr({C1)<@~=AWE8u*J8;sWvNIpo)Mui!oZS00j1BYuALiaN zs?D{1^M+mbQnXlGv_OI44HRe#!KFwD-r~h2NYU(F!2?A?O0bP41W1Y`5FoG%#X^B1 z!KJvn>z;c*&olr1%>3VXX5Lw|-Zh`XhnuYAUU#nRyw3ADeuo>2kYCEuNa6B*zs`V0 zw;UK8h_TXhP>0osrLy<umiJPimsc~aUVP+FRchN=8E0)O4=?+9YV<OTpN(*kR4|gL zDenzSbKi-zP4gTg%{67QYAoxD5=6Pz-yDxA_D_r`-FosH#B5l?Yi?I{Dca7ebRcb& z`=@@TLT|Ra?tEo%whFdl6rxYkz^*|s2|e(fE$vtwPf^&dfV^ne%B$$2$U|A&<eQ-k z#(e(Dp|#z3bp1<+PG+z|dc5iVIDQXi_Tu);HOm^x<EoZIoSMu7X4WW}gUz<%lhsW* zmh>xzlnj~+a<S0R*IOKPOL`L8>De*;nGXd)AxR^{wk6t4%D2#3oBZL&)v}+LvsDv* zv~%7_$>&L+a1K-pfJy*dRCM9T1sks3DWJFbR$kZJCf7m~FcDd%<rjnt;)<onuSWRv zEQMjJEq00)T@%S|DJXthdWByUAE9h*>4k+PNojpY0j5KRyEl=PZmSZcM$rujXGe<M zq*vkh2_`^Qv<{fTl~VC2qAk5R{WNOUmR{)A&|sT?W)70AGv|Aze|#Q_%Z{F1x-@a# z;)Ov9M`B=%x1Kl>a2?s<TngW0`+*%)U#gD6l+<(0Q!ND`K>_JlEH*kO*?LwQ<!VPS z4woFyAE&g8xL?bu7;D!u^9;@?cJyP$4Y*;E6+#@3DYZbXqZ%;|VVSCjTM_0F&S#)4 zklSpw9yV)Eu6K0X&Bn7L2V09~_FrOLwX_Vjw@QHN-!}M3Ad*!G<iw`g*0vLC?2wXA z`h$UBQnEU3MPC)<s^WbqU%HkF-RTF<tr+(m-G0PCn+3g2O~TcPL1nOoak$TOJIE&$ z>x|Gp>G~@DWz@>chYMv`hAoTSi?sC;Wv`6(sr7;YvVH;|N_2r@z+hBcP1Fucq`HE1 zrTbZ|!#D_xzl7W<%EM@?<vFyqKh)Kd+)ncCjj?95EL)fwrjBtisg(^^5#cp7uX4Zf z>JhGLMSWL7XpXjp8IQ_*kpg?O2=nOTj*#iTplp!Bl%gJKG~>id1tUA*zQdJz6kXQF z)xRHD+ho`-!xR%<%{Inflz!$Lkoz4MS-h~WX=s9ZVPF_;Z5S*tk)K?rpzM`(ZV6Bc zeQfvuax%O@bq338Nv5?g!<q6qWilOm%pARWUy-_k_Jj6|{F*zLDN+T_yW{AJV2mf0 z*e<e4OJCLN;2fx_aDi#Egz`b!tL3g_l@OnTzZM^aRCx+uaxmE?U%=L==!YQmAWo=k z27v|SDdd<tIl{n6Lwc8;`Egl)PjGP%;g0o=S?$j;lQu0q2{ha=pR-xZY`fbhpFge? zt$(Y<DcrG4Vy|J@-KOf;$aWxTeBil9)%cX3g%B3K*Gvrn1vBrA&bEI>z~Qh@tpZlU zK*)v+Z+G!Y$IWa9Bi))J2Mf-fnW>Gfa@L55F$X^$9x*oifA{bow|pI#_+q7gVq|uz zZ4~1NpKKrfUUO^RxZYejNJ{;0lZ)*6fi?QDTm<I$Rs80-+ZDj_B6R!mx$hn(lNPcq zI?BdlSRAf!8vTSt-orYC;uQm+(v=psWs~R^IN(qJ4Mf=XZy-V&Y6Xp&KXNI{a`VQn zl2mR^v-Wqbv?NorPW}mky;~|>Y~v4&*Z(gp!2+4t*&9mZ0Z_@=4~*^y{InGa8=1(q zt2D`Np|TvU>#YfaYO07z8fjchz-i0?zOK$7?$0QRfpe_zIx|1zOxd8T+Nk6*EtkQ& z(ry%*F|TzbHWBO*5*yGQZwFDtMg&v+^w0U}eJFC%)y%8xZ{9yBvfy*+JC2?Y-Ibax z|Ih`iN`*7Lk~_sH8t*DiA0uP^D+upVm8-R&!}6isX8C~hQN0b!dR&#M$eE{-6Qwac zhg;w0WF1agwQTAPesk#Ew%QqT(+7|A6IGgvj=;#rY5P@ECkXY0J65QBZ=~PWfcr48 zXqLT%e1om7Zfv%}lWCNA0M`_HZ<X}R)+by^%o}9L8~m%N7w)?8x@OW@H&)9AMSmFd zH0E*FCP;8}?i=99Yc0X)vru6I1fVNYHy<>h;B`0OYrH%eKmKi7=!VYI@!7+H^u3PX zq~eB^bo}p4I;Ayo`8XC3k0UCeuHv2}!C3|K`Ky6}frmnt<8BV#=}vcz>|RhyL`Kr0 zTDp^MlSV4MPAPhF##0s?Yi!-VuI3%IT$5s)${kG@#N(Fe!+LP7O|1ha+_^G|S$e-F z2)^lLsyMoBeosbyYp>3=L)^V>y1i=<$SW#;+PThO0DlNhs1~waO)@lldiYvjrPJU? z@lw@dvS(vUw6dbl+dAed&!n&t07chZg4?03>*=e^`AIYr>{6wiR@v8}G99n7D{rd% zE`7G#8NqWs5%{u#FFdU43(7xAd--jE1V^-K4}R4kxDeM>p`ac`1Ix%+V^wu=on76F zS)Ku*4xXOu&d;^evP5z`D=h&E^uXJCv&l22DTD>IBv@#)FLw@7&qr}K3csg}oBWXF zCMQ<Hw`YIm?TKFO+xlRDFE%yoK?Sp>?=8fzJRQzLWhsI{<iPUYyIm?r@-A!eEiZBR zardz*4~GfwrH4Xdy1ylkIIq50WKS?E<D1QJXoi@^Ewdk0?`Lq=xLU7$;PvEoEXV|6 z_>=@=4j&He;JImHxTW0;VL#C&BSVAaEa`NKk4*aw?s6ZIHR=spsE{r^ZSiB_isJBo z;M1-g|2ogPt-XWInYk6Icy1NAx!hXTOK@ZV*OGx$=ahi}Q-G%jGcGmDI-4oAn=(=! zIZ0bD>Pse?j|94|`<wH-TIeK;IjDZhIjcyNcjD8WOvyQzWNfq#fZF)pTyg)#;ruqo zkGXENHIXJNz<zY;Tc<|<(^+PQB5H|sbmpkAi<op#hZbbnI3mp_TGQdt(@9K?^$K@H zp7;KZ4P7olor<aeuUwOSEhH2FO~@0yD{7Jx9$}8C_iGz<sP8WHJoIX)br*V8_-RZ1 zv~x;Q+t=bTM&F&9jUvqFGunM^w_5DUuFvz4EuP~*#FydKq#rf<l`$mJ)eD6rW7$`} zT+oO*%2s8Hdiw`eYsVb9%b(S99t1By(TKO56lLk`E9$sFA6@sjc92X=)Pd)_9rRwc z!*pFna5Tib?cqZ;q(<LPdjB@+lDJ4q(<9GcQ3zN1`|uk;;Jd*rr}_f)S;aDTpu$w5 zm7t}+Hong7i|wiu(Co9;Lm&J9UR1p*ux{df#7;EH=iAgAR$;7}UY$;0Q9DZL05=Fg zpoPdM?mvO>G_ODDtPBcq;yz1U+6LDO)69>;8ra`hV)1SUFom0zJR&q|)k0(+lxHa4 zCe)k-zK0Zg*qof?Lk(<sw@fappQ>Z(UGod$DXT*URrWdPNppdknmYInXHHqN5v3zS zPK71M>be)ylWq4Fb6op5W{=dI_V6nhozq#P?Wkq<8|qxuFVHTg6U?DunOZ5r4*X!O z9c$XaLXP0K@Rm1gcNjG{?|K0TJleU=+Oslv9bq)fhYWf;OYj6%39-Py<3q+L8_EH4 zAF^nyVJj9BN52PWt%-uG;XbF-)nHF)BwvgBh#V@7t&3;6p5QI6S?%wW-?k`UI|eP9 z9#~ldD?gM!|0EiDL~eSxxD+1f;tqwYbCQ=22WR&_3sM7^Gy_K#dt~$C^Q%fbTi_5E zr3<RZ>FxV{<BDv!YNc;DGKADKf6}WX^KJhc_rXz#g3)%sw!+$VKZC7f)O={ATPYrH z9gpJ5sswGcSk5|D9w#_uSL}@uQjNg+!#-QvTQVH!zycRwKWa8?l8+sQqQ5z{N01II zj>DYNHU)1xA4Obxa|EI2x$Lb8RHn!Akpt&Hul2-Gk4mQNZcfr6+U5%WTbpD!s)qxx z(=?>o|Ba9-{1NC|sCH+BJJEA4q~qVU3KsvSRmeT6@z35jq?x&`28%0T5^siCZfz4S z(%()CZjEKCL^P9~nBrXa9i&Z^PivDtD`SRJEYjJ1mK9T^LmT9UCNyZL6>aQAjA#9~ zW>KCV^kVQ-O!zuK)2PzABWhgG`{L;{v`66FzKm~m&tZ$RKHrnokS#q+|MUrZSnS(b zmYqW(%E*{P%DUGg1tnTuZB>BA6-2Y;&%_0$<B`rw_ftR*5L6r`lQR-mW$eNJ>+2iK zl|#}2Dd*H$Dw6vu{AA?A8;-v*Jwe2xvrF9l&EcI0Xs@F6&L)%$6`t)Hs96hbhi-Nk zzj<<ZvQpc`gA0$JsdTr%;vs%_6g!Y#Odu`SiMj=tcw&$Qt{+4l5EPb6HFcZ6Y^_TX zZw#-+j^FSm;D~3{NhEWv#X^?LZ!)AH<yw)8d?Dpz&K)z$nH}qyS!xlgaZN6<*Vx!< zop}6q?_QKh^DrJ;ScoGya7mB*=`5j&mN=Mxbls^IR18d%^V!;2s?wn^8htWSDcm*t zrrcm{m)dSo+%xNwxGjT3INbB(Pt0Cw<~9A+Za!m2;l{g`;VsEws6$Vn@hpoH^aAV+ z9w<vMdP({+RJ<3^t~;aN>+Im2iOA~-CS=PwKM5y~t_9hyJ`b(UZ}?W;7Kq1`Y)4S| z&5IibN_pdNYC@O+IWx=$v-9lSp>nf7BekuJ+;Uc#_b1&Z=+=!{c1%Ayf+|<wmJ$=1 zQ}^@J;!&uwdQifhclqtYlmy-HC3+pZHxuw4Mh|hB0t=f7@R>~~#$%N2_zj^N2%6Q{ z?9I<y&D?B16{|s=LmRZ5r0E1xv2RQS58<oF<3eQRNLxK-|JZd$Cd<Uby(29l=h4Qn zk$H8hQ_4cW4YV^iQLjgJZVm<|&bemJwwGaZbU{<S5!NyR_<f8Y%YnJ9g@%>rMu$Ft zrGn=XwJqS>cGl2pGo*tCk`Z<9QpGECyMfK@FG1U{)(hT^h8B!dE<tPki}(}eDg3ep zIpJIF6lq;-bdc#R+(}rGbBF;y3vK{`hR;$to*(jg$SDT8(N45CFTEX&zNRc<LsSCM zoDk8`tuxq6C-Rq_##}q2hgIsBxR+DwZ5{SgB5-Sq0QfTxQOEJ+7+>R!>N0eyL%8qz z-6)RMgNf_k^^7&yW_L{cW~uh)GzF%Nyrto{%{9kgT64JV2Y2u;Ouc_evv}oRWAvPJ zX<;clF=Mr$(B22s*=R-s#>wN;{DJyk(*51kf@_DnvgV_7(y~L4!}wh?Nw;<CeH6#| z9IDm_*e$3h?p{|^nwpMvU0wBN`rdSC7A%gbQImh)#?D1ln6Z7<<n{Hmqfz73#T%?M zsPAr&=_maKJ&9_xRoToR>|L)0TwQ6IckPPmp^st|T{EP{2NWt20eXF~X=>1pZ9srv zHQm6e%{LEhZ@R9T?2D8ZmlPUo+oK1sn$*cLoOFHV$_v@+z3y#2=o~^^i72+vcdXyl zSjlb}s;<skI21XN-Wo;yyt(8dagE%!$(5g9a_z1KU;09`VN0OHY`ydKb9}RrsIOb+ z1wsEEIO^Y7)^G1OasG0u&Y{0oe{Z^WknvU3-xTUE_ffB9F0iANK^It5=v%#W6UNN3 z^$dOC0$o@2Auo5q>tKJz+WC*4_*`0<Yl|kRWV(~>%2r(Rk4KRi%tH;KjeA!=q^07T z>;|h~&_5<oV)xG7)4SyRSBeDN`9RUX<IFNEwk-YzW79SLo5uFP|451VZ{Lr!r@Ki1 zPU9kZGZi)C3!KDHfVczos+k`F!fDdSGy^FA^(~`56)Vrm0l5mh^Z_2NQV@iJT4hpy z?ZXl_^0M5#vDsJ!r{{8qphz%>(u(;aK%apA3_KAUgsM(hj24av2Ys<m0+*ywn&;V$ zOcd9XR2Ol>T(Q^`AcSfzb3&9K;+c~f;QpKWysXFD$JZCx`yK8G1Zm9kD8o>hegO0| z!|L96XoK=)f<P{x`r-iqjQaUEG@0i?LgGL4BMsNfWCcWMN|U_gsX0#|YCZVk#q%0i z3N_j<7r~F<M?;0h-{Ei1AxeEjY>gX^P!AFtN66H|>smYdoRb%r3Iz<zOJKp>dTg_% zj9+{`U`bDAmy;i6MZwd{h^C7Q)xOuI%G4LHGyIF`{7+A8{9j;a9*K*KW&{5Fgf~xu z3q0K~^q-9xVNEj(QqlstA&Eh0k3QRquc5=C1bi;>N1r8LMxwW;NA++Xt!|Uh+y@19 zzB;c8hR7@FpK@5?3Ff^qtJ>g4;{EIU*ECcVnBfFWZ(o@WUqg;a$duz=bOzi4tQYqp zwcU+FQ7op&B4a0hVsPx2yDT?99}E|1I)}nu7xz@R>yNDa2Y+y7hTI5Ma11vgyay2c z2Pg_Fj-3c?ls)k3vXIUD*#adk7BH`87P<OCEb@L2kSs79GufTbhljE6`v5xFSyi<( zxc_6#-(=Ne-7CjxR?_^i3t|?5Wa%)3V_aYNA<;?J8|_49K<Zcu6U`PyLit?;n7S@L z86W5JkBQV~^0(^h@q)H%VUMn*-!v6tIb`sO#}qmOLFtp;NEe)-l<^<d@TSU3C2j$4 z@gdJvWq9nm?z;#O%6f^+5PK)JQMNw$Q&Qjn1%lA|<;J(W@v^RAdui~Bi4G;&GP^>@ z)3@$c^1>}pFb`(g@3z9Ns5Y7+U&C|`&QB6hN1*EO{K9))DQaUE71#yH1g~4=_?5)_ zNF#DzIj0JQVqO}md*%3c7kmD161;fmZ>wqadpzY>TSJT>%F=qW2Z9r9%SLlwz2|3D zm`h-O8u~ETG8jm<$%>sA5`^FSla5L$IwHrg8u>05rw3sx_a5)*5-vLJr~;XEk&zh; zjDMWc-+PiCrU4A0&l_nC+Cj-uKcklUg4%8=$cv;U?>+qbgpI)<7|FB@=5UM>z-CZm zeinTI*}5sB%b4{b>kTt0U)aS#FdD$KSJjJNnY?;8(K3f<KDSTQgFh@V_uY2-HV-1V zh+AeKUQaJ4_ey;C1&#!m7v}uA6)HT5>3>1Q{+fahD1nAkbjdvyS=tMh(kzu_<3h9g zAF0GE*AtG-XONN^?H^<x59mJ>>}v@+lP!l&-R8*TN((P>o8FzO^SrepS!^w4v#!>A z`|^Hc!9VF_@PEFu6MA4ICYw~Lr(YCs&3I%*77b)~7L-}5!cgQ`{SW(i)*CJ&D6ZRo zxVG$lBojQd{q+N7K9Yl4U=_|X=9)%dO&9I$P)hyTF2o>{&G{03=BZ)rxn^S%`wc`k zugU4D_i1#^{+5YyFRC7|49wupv|5f}tXm{_){&~RhjNs9CT17a!SO=vOYkwzF>}EB zOfeiI4!3`0i$6U<#_vpTk^Xfhw?As&mCQk!s#?kM(e&S!TEhR+rAk3r{ja3mudshz zUx`_POw=cqo~2Vx6GH#>S>ma~aQNcw)2cJRz~NU`Ap=wH4D6Nb%kh6-`yZeCuWSE# z>EROGETp@F&-GG$nfSlHiqHNYcu#nJqRsSAIz43(XFyS+vNwH@Fz+1dxBV)re8%PY zVC^G<x<RPd++2Lq`zKvjR_57}aXh>N^^tOn_)Mi#>&DIQO;;`{3lA!v9)yj;8UFFl zYCZJKtYiZni3(1`75tv1eJ_a%YM@ehAM86Ewyk$`b&p2?%AnbOpjXt2Z;89!!f3NX z`&1MN?ITO=xjwA!KAhJN!WsQh#^XEJdHXcn7nKV1aOHA{dTdU9l7aWir(l`mYu03y zt-BI0=XF{ndTiLEGp^WVl-<3pb}RKCzHx!w;QwU?o#@V!=XJ}9`i)BCul8@^>M#cV zL#h1D=6fQ-_rw*F#9eZP#TcIc!(>i?Jx0|soD-oxK`B4G!1BU}^UX|v2~PZ*--D>~ zh9sJ?qCs%C)rWP^r2Q54vJ0NV`&;;D%1>9fg!84Z@d$$by|TPc-(?HTxs(DXTu$1? z6O`4pbNA@SP@&g=wi>}0DOQkLVF>1W(L58iRchT_&mB$1VsWX>K=jbE$``ulh5q9g zWR>6SWY&%rm5{8ws{%ya`xmWne%H*No(1w5Y8<|f;X=x9lc~}D+c{#)OK-Vie_|rW z`46OqtH*-#IMlIqPPT6Hi9crY$*S1>Tu2X(h$Fy-;}Efa74<V}+(YeeuGn{rKg9dI zEB>T&-8@tHLrNA=F<vZh*lX7p^l@MrS(hkmo|gM_<(2uK49AFKIyG|NhI2&>g!BSf zvd8!Tf*t}0OP;%QfJ_u95HA0py=Pa(073}cMIeYHNeA)}uJ)y0{?#+*;SRH6LLZAO z_?&_knz|I;c-?)EOpV%C82E#R#tX`|L<JRA>|d0ZAazK``^p4o0})tUlxUisC#;Y& zeb;lnF>_qixDg$R{ds+p5~8xodMkzHm7EIHXe7h1Hd%Hd!HH0%IU0+>IKJY+O-e>l zqYJ-dGl0mU@XF$GZobN(vPmd7VAx79u)4X<UEnU(F&yk7C>LZ{b2TRZLriavN{Fn= z`%uydN8`Sn$rwI3qH4De>4@*^IczerLhd+!o;5KnmBjWr<so(gk@DP~i;eifSAI{2 z>k2Z-16P(TO!n1Hv7Oz_%!>D*stb0Oy&0-x3A6Nl(9Op^;>6$}iQZ}5PDaBH-9r=h z?<VYX8`QT;Up5Bp9A!nE_ip~N3aUVJqenP#aV2=js_^p6W@1pp>=qd+Li0+sHdbA* z*ATwr;aJ2RZ#&uX>RfozsW}(iQF9<e^j@xX^KV}o3#3CcuQ`-maZF9#$eFH4foh8k zC_*|D)_rgXWL@{I4`sT7IsVIGi_`x8xn7U<tLKT5b#vO{X~yN4_cf+vb&alFcWaN` zJ;%8UXd_m>CXzSwoeC@Y2s(L^ZR~kICvI@w-r+vF-R?HdZqgfj*Xdw+gAN$<;f(bx zcvB^Lpz}h2B%n=}TO5?Rry!#i<6Es@@SG>7?MB0R;kAT6=_ci+qrrQvZfxfPj11~4 z!#NiCIlhEOjsmn=nXHWEJje^7x(<1DUIyB>(k5daHL81rMGTFF{!weaU_H}toW@k= zuG3j}V<o@gSl_C}WX@7{B>WV7F?~MC*i{`21a30m_>wHeu6`ljJ!7;pv)6aMo>9)> z?m4tt(4%1GYNB*Q!l|J0QLzqRPYXL=;)y2{Tw!2%mTu4fuO9;a?aP0`ZFv9BR`KwE zRPq0VG@IW?ABO6o|3x)s%lde$u=hEfseCit;r;L<<?5bO)6>?IuBooW)BRJI`sD-$ zh7{P$+tq*0m5YG6@-161^Mm>OlT)#23h_CXy;aSDe3?(Zw%=PFfJeso&iPOFf-TpE z@9FpV>uM|HVX-6?K7w%}lEHFb1`@;*K=|zwD*qilpJ;2IZBN-p1qaVFYm?2`)!4N5 zH34#M|7`L4pLEQ$SkCn1P$fU^5wX;UeO?vR-aBAiSd0>Q4>SijP{!}+#bvjVj3GFY z?vX3=z9XH%JA1E}W?RtiLSM~>`Y_BU`HnPy9LzXEJo`y+ytqK;UBgZZ8NQ(c6!|@1 zTJsU7cUN1HpTtHl?>0d(L3@!|OR{&OC!V`Sbd*#`?<6=WrZdRO&QGa+9TYcrjvMw) zOY!a~vk>#JO0j6Ltd~7vKYK9!#?^#YV)MMEGcdi=P|wG=c0FH??YWlWc2y?}6Hk`Z z=#P$b!~n$T&mu$TQ`yd*?dL@z9;Ab$TzCPRRZfB!!Z`<8pq=($0s@oWpE|DG%;|Y- zzF(cXhfsOZpWoBs|3%-R&Gg%>Pbnn{oeHsp`H&d~jr9t;xwK+*argpxYK;S5Z`09e z*^2?itUEY|t3u9xqRAoO`U8!v?UFaspE}qS*UdrJIfq;!!e<a-+WR8;ijy%2{7Ns( zz%X74!~d{7MVzK9lf3`6z*5=Dl~8NGlwspEf^pV%Uw+;N#kltxlqERjyF;W6zVAup zny5GLhiGL0W{eHAvavgNW$(b%I-XsDT3T8bzdM-c&05f$OfPTxr{1sEFZau^ase}e znB)fEt{t79?l%~Pvn;7kIE;AV282{z)nIf+D}LQrGz^>=9iOfrD4Xt+B`eo#NNpZ7 zIlaC+EB3y`u(I1hx61H?gC610RG|<*t3iX|EZQ}Tv&=(i`z>7Q5z)XZB-5Or{*vT0 zzC-MYtNBqZ?p6_5$<nsSlM^7pSrj|TPHw(s@r~a)anw{c0{$?sW{d=18Z93t&`kRK zo*TSq5iu>B#163`VD{=Dd0_SjwzltFm%VJd$k}R2L;CEh<%pozs7FVPHXjrGuSX%` z*=E#o<hY9&4jHPgU5s>nnw1D|Wv7mgy$0Y+%Ef~(4krvriAg2qgcJRaDhwMW+6C>5 zVHSp26ExMVba{IBQ1;=#bG(E~3<nMpp3Ka3@5TV5(U0Dq(oQJo%eQ_WatMS$q4TS8 zg=t^jq!8a|(vIE7SQ|WfOtx%fdE-&fR;o$QnVht9rx!x#xiHak_WKM@EjI)w)R1Sx z&(<K)-5BVh7CEuwu%svMFxa}tAYHO;Vz4G1nW}~Z>+;UICXr1K$*G&fx;pwSlxzgO z_%E<dlv4yfuf>kJuEV3szMQONKLs@NnkWgQ(%~qj6>=8sH!bu+ONKqx_LP+r&`#~) zzPpy@RPYusA>}@*w78f~Gl!M->Q$>lhONejcteoa&C1rGdqyB;2qQA1wTK#mnvI~p z&%Mq|#RTvPEq|@=3K!zDCq%`C1kU&nnJqF;#D9B_;*F$NfNT1#rD;!2CIw;wpJkgL zO`8{!10TM)$}B9ORA`=_lxv`5JQACGSGRuKKQX$4)b7rV9?^V5>Lp#(V&g5qL{Yk% z{{r0tx|jz;Jo2iErs~mD!}RpLt)bVyJ&IyvULF|Km<_R9UBHcEzA!|@#vaJ;8u*Ik zHq8G?r`ivfFq~de`<5b|5}5IEqx1H`(QK)}yFT|QBkq}gr)yb<tSn3QFRbSr&A=i! z_+MV+EL4Kic}Dp6(+e+jzte0>S1dtAKVAQ%quUKdT}m^$+OV%MdYM*B@yW>jzIncu zDDJ1i#WC9;!z>S=SIwpbm+k^}%tihwSN#bY+5Rrq-hNyW!mh}9{F9LyA&AKAQ>B0Y z>7R60%(aacMHS$fzCHG3OCocrk^tnz0&P{z1|l_{D)qRk{pLGAq}v*d`K|Rwt1!-I zbBEL1dRC>eGFjX+^uN~m&b-xKJAK!FW_0Vwc;6`=1>-s=squ8;*2IS5$Vq-yv;&O8 zjg&=kKDMHAbX1$Gx4--s{JmKx_I03qWsz$mOR{TIv*yzRv}+H_cwe~I1v1lpyLDCP zH780pnw}e;ZZC~)6pl^j%V!Ob4XvoTMXGtg)gxL{-t}yxp<$`6A8&TpFBYZ)BYVen zkBuEFZv0`Og_OT-yvUchLQ;dAWe4CawbqXEaK<T8LUKY+`BE&#Ciy6>;q<8MlJ{?) zSGf-mdKs?&3cykT$o(NRJ3+f=oVS-|o;Yx$-wm}K4P;2(d)Xs8fPAp^t22mMs(p@2 z`8cU#)Gg)_SMEO!zCu@KCS1**O5;q3yh@&my1J{qppg~OO=%y6A_(i0{HJe2lbTC7 zZY*U1-w=?#QFb0-_Ddx)_z=p6X58+a;K3O^1I{H-rz`un2LGEn>F$_TrprOnISV#= zE)>1;dDbiRgrmmDF$2sK)TjIBIK^IH))#0v)~V{v*7ECTHZ5!kITBw3@L=ev|Mb<) zhv}o>R_B@F|0^Ht{=H29LuNztg#^Z&nkV{IVP9V+!DLK}gIRC79B@AFTr%E<Hq0xM zTLdkCe~n7XU&GqvhExHEb^ZEfy0uULAWQ;Bbyd|X|EVka5T5T*di<*DPddl{Nn7#{ z=ksjbPS>11cCC!?LdNqf(+!ofx7)5cXmlgybmWAw^xspSUtpr|M16X3;ekZxGs;3U z2*`(oJjqTYxzv;c3ffeKPO$1<^da$}Uv8!w=o)@Qn@3cp;ebR86?D>u^Idw*Bzk*( z`t+p7F86NChJR!)8kt-LTdN&BNa}PDp}&dpQoF$QON0Z8KAoOT{(n#k{d=(A7lC@O zYrCB8%EKq{xb4@^=&yf6*~HPa!O!N1La)`SsleC}9hJqjbOk6*GuF{lNr830HF{p# z^;a-H)F4{V>yfJM^=1;18HD?^nwDl@P%@*?z@_!t8Xq~QV@&j!fQ&UEKdl&Tp#xvw zU>7_YMZ_!+Z>V;1&e~O<ZeAjfiOE&D4hI2_f@Az-?*x%!Vo2q6uLN9v)jRRkVO{4< zd&H?i9!xp^*7dZO=CeH>DJR*BLqrpyvx~ovMkCF4ZFeCKW!LNEa_p`M7eS!)In$t3 zk4CqC>dW9PqJ^tXubau1rVJOaVUGQubi;Zp?ScWTqXp0C`j*0e`8g^ai61ptk0Xr@ zr%=j8bF}g+OznciLa)NvA*u^i%Uv`E$?$j1VHfA8-XpBq$`^GNxf6$VnQ=?*tisjo zzV99mX`D5^S!6^q^a5gLsEtA7Y%q;FmzOo1G{3kih1tm<yR*A=X}aZ_&Eb5dyK~tW z3le&?d8R4r!K3q_AYkP9OkSS#h4YPy9+r+v*=|gv;2W74Lh9Is!jW*bZ|j{wWWciw zOUKQF0R08T_(!Ng9@kgWVSA2~nvpxp{3w4(j~|<Hbj#Z?7FRo3kR*szJcj&~Q8?ZQ z4vyzSDSe{ngs<}1eVHSaJO+-^F+y9lDMj6gCOJK|>V$}$@ZA0p5yDU-AhI>yw$8Q1 zC-kkmkkX7_%6Ls^2FVsig;S!*kX>AE)@SYI8xEX^;J}fN2B*n;*{;Jg5SN@BF{X4D zuduA&HSJ(z_8rI`r@k*9*_{{p&Ba09&-gNLYNsolF_jwmp-ci72_*g%8ZVLgQ)GFO z4A^w)ym-*ZDu`=Oh5^FzND=aapSLy))Y@ExA~QaS)9aRMojVERF$U`hJ22VDX822L z4ks!><rEFrtNvIVot;N4e=oLl>X$3+bdi<Y3eo)OEyJbHYX^nN9BwRsih2>`U!KdX z$O0FDPH!j+K@4Kp_e-h2B8WmA;Y8dl75A|eBn+}ofTn`VG9+_(1^JAz(G)yqWL@;z z2M2qkySX9GAA+`UH#RjAAAO=QKq*|KIv-CK`3UMDg#?99_kSHA-JMy5aspocK0``5 zIiYFbCK$4z@}<PJ8pIGLsj8~ObN@L5oZ&e0{rUfzP54ib=3haD%pT#hOHo9{dOO({ zBV60CU0$W%?(Aa93<>H3{R%iqS)N&J{T1(S9|TP3QQ0iJQHhd<Xjew@Dk@Gez%cD& z=rzlO;=V=Zb|0|~K*H>0!O&WgZP!~J^iju-!a*}I%3wSav@IQmOLrtr6s)iuyb(>o znPRbpHlB~Xo^vn&ee(@9A~tH1Z9cq{yMl0&!;dAtOy?LmUJbRBxv`b4T<yuqx|<RR zY{OKX4A~kGyFxlG@phFde*L6PVnO!p+pX^o09+{$*TKG7J9_tx;<vueF{^6ID8Rb2 zRhQ?oDA;5|zP#lxIXuW<fOPX`;EZcbkBr2(s5FhdEgHjfEf?;fID&+dNreLLGB@WI zaRFWSIH*F3bjioUE-=r*&Mc&p9ieCEagzf{8Vmd#OSD?uK|M&Kzsv9EdzSqL@FR2Q z_3Yy~C<T*ubTzG1Ubg+rlv#uE*%n!5_8E1dUL^r>buy@kBzgfwKlW)dV**(zE^fLV z{6pWXJ6&y2JF|QFScZv~G_*DtD%$a>nGn26bKVh7wiK2DOq;@6m04GR=gQ;~Lp5jo z!V3>qRZ@!TCn>h{$RdIZ>{ItLo@;V7MD?nbQfv}HXguIXVy!wBO^64>ETbc<ATor) zVprIP(^Xx@tkxk0t0nN0nUHm~Mw+l%tf!<6@biBG%G@ih?E;dsY0a=<bkb|aRLQgw z7l_WEbSrbR4;H02N;$Cz*Fa>ab#wuoxEVQfm^;WyN<&6fr{D_*QiDMhN16G3^ZFuj zayW%`4enivnI4U`%d97YjiQAVjOS)xMkjph>^Z_VMh`Ka>JO4iIDPsg_!FIg?S&V5 zIYLxqymnJ#%>m(^Wp>QVIhSjflr*J(4iR}|W^sqi*~T0+jn}G9tYnHzg7@*cq`Eny zLR?3UwF)_MtPe)9-m26J=wz`Ac?c>zbK_$NZYB{bE(vp>NahYo`he9CkX5g_90G61 z?b8GV26)^Ag%txWyOr)5f>W+AFpn*)%`$l@+G}DGIA_*W*Pee$ZD=7~O$~NvsJ}WO zjy2L4MoB&leFS2*`WXSYF(2^t;W@EYRV!d%U>sBn{`6VH{`r4obhu)Y8JFxRa1OKj z&C54pWO}Jrb|uE^5ycY+A1+Qr0A2vn#O{N$JbS?4^frPLI@kTFb$=T3$7TDA0D<oO zIYK~<MX@Y;WOckSxq@XWP<ROg{~FCUj1L|dM1Wsa!TqnxG>5wDUb;GxrT8eirAFWQ z1um;9>3UFTKe=sRQ(OFelf%z})3RQlO_@TjZ=D+>V-{eDbX2zQm?K|ag@yTvWOjE5 zYoAuhJXBarUO24#Zt3O7s1Hw@2MvrY{ChP=u+thp^GkoIFfu$w*f|eIW(D5T3sG#U zKAm82A;LZIdBbxqA{DloZDJ#So57SWi9SzZ>rpLOu#>~j+RKq~Z~ox9Ph9a4CbnW@ zKGLb4PmxndAb?*P`aQ{9%n+H@WQgpe+Q{SX<P1Ie>&XM9V<Q0-E}nA-ylcM%;ztYo z$I_e?+lVA+IjG4Ia#A6N#9zDu;^8eIHk!|YfC^Iv4(0<YT;04gX@qlN%FP{pPvG>? z>r2th9QVK?EMNO}1#LI)!$er`?#8KsI@Fra^y;qs^eVllD*`OgvS90~<;2=;0eu6- zJI*~aOM|^4n}vqLxm^ETqLG>%;iZ2q(O3({L`<S4XKh<Y$__ON{xSC4A8Vc87H^DK z>_I<|JrB)3;!inME#mSk^R;%=<U}-niBp9*det>GU~rRC0e;+*&LZKTNBQCugMY_+ z^bW@)T^_EKk-Fe&z`(#ECBL%{v&2S5M}}e=g*D1v?0^G;6snp=F5KzfEUYo!#K~0N z5Vyl)U4wY%;xh$6PN6Qb5S6iz=;eK%+M--q8dC7m6U-sd^NT;<JN7p3M6W#DV@b=0 zl=R&jxf1v)Oc!S#`>wz^)r+tMJj<t<YBW&7^F^E1S$}yRt_T_Y!d%YD8);P+8*F4| z<3c0h`GbV>uNTHq+DgB0@U4>mXTC>nM$|RCU$`We_M}`FWG&uQ;QP(jkgUR1a-I&H z5TnxKM3bB%aynumX7TM`9?2q)`6{-H_NmX36y5kGy-05@&pOIvfK{ojiFfJV(@!L0 z1lzcMqYv&^&aPlyPpMHLmxZw)Dkh?}53&JS3PC-1aE|A3-?edIeW7#!>&x95$D$fJ z;!{vk?Tj%Xao5S&|NZ`A6BF%`W<EAJusYYYN)?34Or>Y1Z$$kFJkJljOr+9t-@Ix1 z#sEjD<L-mZc)q)bstrCbLcffPlRw8r?~f{$KljiP7@}QD9Q(Tl4~%*Hw-z9|^6#i> z+ws4p#q?R1fnNJ-``926U{>og8TR<{|3!8Av;W;&4*!3?<&8C)P2oLmyoD=rM@uzl zU&u!+B@bTz=8E6l5_TwYyG|{|ZvdRepy|}0U#=55jf_hLQnFX(mJqg+3^!Z{LR`Cz zU0f)+lWFRGo-99mYs#_O_SklrY-Z|sPj$^MDc3TK9tUu>X>gcjV5QpxW34$M)0+|N zIE)}+ms!Ra(Vecy2IdB0rTpAJ4@Mo#&Lb?w%r?_jR^v@FXLPJM6^I-O6><MEI0Z3X z#Qv`4a3&sH#}sd!8cegS6TTv|t?lKG4Nx&O@L**}&!!~=<@!Jj7PZu>Z-_f47;%>N zil3Fu=pFKl=xfS@@C`y0{VpypSJ_sReK(E1szsfD`R$t>FQPD<SfUpRtfD}WU&Kt3 zye;=lkWIE4Zto!20Gd#DISlpl!8uOFhJiH!x9R{dr*6oROC5l;B31}V!z6g;d~#qs z;+8?+YNU;(qvu5eq`z2NLoNRld_j;}me#&8BqgbiikCmf3HP_`CqJIc!4NM-B6MVc z4aidVw+Em2`UEuB?We;`=g#JTSgNTFd^=mf65U0-iZ^GsKc~lJMjvv;&2a?T#tD$b zCcY{Cup(`_tiwENzgG=<lt6h`QKrmRx`s5*ywCpTD}FP<-SUMSRq7Kp;k$3H7O7R| zT3FA%F(-J*aFi6T%=pS|&ff8_Y!*V(f-N)S0=p#<)JUnwq<R-N_}382^|2O*iVLDy zFM33KeRgTkDt6(%W>~e$;o}&Ekx3>|AH~jB>Q5u9)E(%WyOJj4nokTGPrHAwf+0GQ zr<*2A!v&LfCp+f4WuS_Id>#s!?%gp}4uduDo5#<cN2)|9J=NPv(_z(QO9FY!$=FX< z8#$ghJLn1<NQecCE`=NXU;5Kyy2Wkxw;637qlEhNcX}>3T=|LqzieoCo6R2YUxxJU z-{kP++v(vQY=`|00k4I?G+G2lVUX($!AhCi&YFaxd*pj)P7BtX_v>P-aWwb<4-fC- z`N$KaonL`S5P;+dkY0gUMBvZ(W(po`f;V8jdPP~|$|H{eUhC+(yd`ho9zvUk{@RG) zzU#bvSdtHA)fNUaxShevUIpuBr6mFJ+HXF`8LQ>MMA9isx(!9v$xE>=^W8eQLe3@^ z^jd&^ivkiiyP*><q@dcxi_V;25M!1vFwhj8z6Ja5HsUp4Bi@}e5tCZxjD?{Qh?A^4 zZ#i|;jamn3t?;>eMehMVUoodtX~7(Ad5Ium6!L(Igs_zof;q9?&BJ+6o4-ms$V-wS zzZU7gZp5)kA7kxjqLJ)H!;`Hs3z&EJE_s;_`dXcC?x+tJu^0YHH)<Jlsp;=6b=v}E zz+CC!XEi)6$!DA*e+cko`bn4IIKudgG512iHbPO5s?AF|O!hy>mEO4Y$jd8&NU6;2 z!c3WvLyR)!{?O-j8Et*NL$-{A+T}d>aKJ_~wy07bHaA}gv_6&4ei=D)nujfu2wS$M z9T&U>>O=O(sFkN0r^;7=M60_I2mj1gF|cs1q8S+T`$-v^J7;icb~Cpp&$;CM+)n47 z5QlciVI3&eA|AJ*s|8AFkXuOej4U4!q}Rs?D`9HfO%noBZ~iywgV5fP-Uww`|3$%A z_@*q~>$@RK&rssv3;bM@ZX4v&G<%ntzL=zF^**J*2X2Mz618pNvk*EE${P&yfDTdg z>Oc=jAInN%Yf%hFM`M_z{5zojo-S`;f`P4AfKHMMUVrwyH(Cg1nxy@5Z`)6;)GHE} zWaGgO50_4ib9{kUFN0WDEk$3Eu+E+A)m}nuk=DVGwM8y!&mg2C*7RhoodZ$=ZU?sC z@^Oct3&v4DYU%_<^rKhx?Q?4jG7I}mf`W<K31;e|Zq6@HPPk3UWYoi$xQK51Tkf`H zX6d!7^X)+nM*Y`E+G}b)_afTeUkIry=2YM2U-{1qtMEBCLF**AI~RRf(idBPH~>WY zUy%uFTzp|FCyy^xG|8ZVR8Zf)KKC5pI{N|01bcx<0=n>v8Z9|w0E~!dou&)_|4^x1 z`v1LD;n4eq=X~r|&EJYaMbK=(-}T95iC2F+p`<o{J@~tf$@+u-XrCloG(O{M9*x6Q zsp^{@c%!&6^lUOG%R`h4*pPf^36xUPNbrM{ySyPVvsm5BUD<61Gi|6-@tK8Yv?c6Z z&U_Dan+H_vYEDMEV(6tMPrCTWNfisa424zt1I)>L?hPZSn$~UZ5Pdfz6T{X$GVwMM zDk+y?nd%YBj2LPhc0hGw04k9T>K82VqKlFOUf`e0-;IFlCtRC|yUw4N*3oJ1ypAS0 z<OCmMvN;WKzWCrU@gkubHsEk!$3V5k#leK8!A5c3AVGyi*U^2uexPumudsl&)OD=O zEX}Lr+4f2IyNaQ$w0-)J{d<khxprpPX7dyMf~In0J5WS+KzDrL^K+v#P)Amg+I|YR zem7<+{fK7m$z7eMrIEr~Q?Wuj#VJ?J&Mo#Rh3w39sWh&2qVU8(uGPLHp7&)T)g1bk z!qfS-#IfY^9(^+bj^jMvt^_CrcarMlFQe4keF$1-O$%*!=YG$K)UNU-TF8u3FL<Gb z{f)&^f`Ew}+nj4J8};?M(RC-bG;w$%MHW%Dyi|=b-|ZDl8GsR=A$^yb4AceRtU+N# zmP5HSGp;l$r5W@}2y-iW!2H;xxHm06Pi$wW1pST{+I#1@MNpi2p4VZzE)2P}Uvoo! z%YmW2n;nN9QYmm@X_?tCT^45T1J)udi=UZd3*ACa`7Xrq0=`#oJ4atwz~8KK8-{TR z6lEj2++$vLL=d?aRWuC`2F{8oe7oW=uNQr>e7zcf8y&v?yE__g>MUwgP{?5c#q2^# z9=bjqvh3gMKydHOf)k}Jal-zH1uXe#CVA_F8!3ranIYxSV}T$%=qantGO&_v`pwmy z6vdJ0-=VBv0P-T6<R<07cOhVvZ_B10dRrN^l9-fhAFW&ZlrE|FR-}sXYakESchFtO zLQ5*w4wV6k%pFu1XMU1PWxL9vD<o&#%YAL+Z7;XCk-ACTT*9u=qY`RyNDL~+28%ew z@|}HJ)tg@^nf}q=(J<1!%Rk=e7gLd+nf0)w0S8Ky7=&>T@DSl0K0E;7CetaJGUU<X zwN3~CA+OwU$mtSs?*zC2tBa*K#9MZpdArI57`7aowe?bmMAfSGeXzS3DYL~z%MVKO z%KI!9r-GW!IfP%HTaV&yGKMK$C%0x8=wq5$0`!CY1;fq5oSntlqlO0zC8|vF@qM&} z1g)$pw3CnE0bED#=X2zN{YVFx7<?tW!u_ZYSGTR{_)fb5NxX%I;E@V0AD}=(Of^NF zD0OVCx*^?A@3`<JZtMLz9x-}jXrS7ow*dQy=nv@Yu^~-Sd==8zna1J0X&jzCiUxrS z?xdf&8*b3bYqbJ3o_r|tyqSvLR(B)~;;_?kbvbZUIdX>y;e%_~E2@4g_VSz!Dd0Cs z>2O9<#hO6+IE7X}Fl6G)`+KbmcKKoc>VpvN;ctWHcvVeiBb-euZR6{#zj7qy-5{Rn zRkbvGmN$_F;ZcGmDOJ8C-Cc7pLfgCK9J#wZqpexaGAdp#Ev;qVm~V_LF6uu9=LkRA zkA^S%rWDR6u0oCD3PMV$b3VGf=+qDiyn)oh^b?43I^%OL{lp+V+8APAlUMA|%EeO1 z@Ju-y%^GJFHy|%(<zcHC_GKaC;sJ+JF2%wG9XB^*xmUVl3e}Vp8APaU$t*3hMbe<$ zFttE6Srua!Z7zUxpI6DMVz88&dg0wM9JjOdV|Xn-D2NO3LSv?}+NM)guMnxi6=*Js zG|}Vm<rjU2zIsE>X@I3*pqVO@l9G~;yn8){_>sbO&Z;#%rW;zWcfu_d8+wicwL-j$ ziL4X#pHk#_OUSWsT}2Bx3%dxD$-kSczEf9RD(N!eIl8)jfo^frsk$OKrp4qVeAvp! zW}K<J+B(|I#oavY-J1+pm4D|<5dsm=y~xpiN8Vm9ouGPJTES7%A~59c@ssARi&9;? zIK4Dte*;%zz5|Z(Ftp(D;Z1O1w*Ka_MP|K>Z#GIpJ+vFyRkRXJZgDR3VAjLoBAkV% zGXrGI%UY8<<AQQ_b@0$@%kr6Uz34Ot*~%%cHh74^^3E6s64Eae${z6pG*}SCOW}{n zaDfz!MPN!KFItp)SC*sGY<Dl7ExL&6zO8V+%Oa}srl3-$GS~=RIPAZ(rO~Hq2L*cZ z>3y!&*dpo*Z26|lo{0KNUGfA-tdfJ0WTAh7KRmow3(3kpoXTwQLlS<|{)o@R6}{7y zGq<(tLu7q*;M`{}%PsokkI{97-}6Vb^kI`VChgRtyDmaMlR8K7`rw5)ckT43tN|nC zD^j?p4;54w>V&2BmR(P;VIPf%6nR!QyNj&tX5?Qeplp|xQWt0tUaAZ)*jPuo94^(v zpFnrk`2(Uv3P+cjgW!wzp6Vd`b}x_jGyCuq-J{~h?K@eo76Ogg{q*Vkbb*L})9a`I z`@D;;Ka8d7w;|nxp9B|?01Mb7d2b~ryI`RuT!Kr!Uchc#Mrw-wQHc?Gy?xATxJmA+ zrNk0KS5r=7*Q5k+F&Ze(+pQ|azzsXs!PpkRwZ(zyz^4bV2Nj%dDTG!p68p8rUIh~+ zN%&3gh%Vtf)&V?^v?a;+8_oMv5ggr$xFYgT_LOnfs?_nP#@<p*%nf61sSFN1Y0g)N z-*hX`;!6@(ava5dh^-lnIlGG~q?nJ^_hd!y$_~4`r;J$l?ckGCiO@>~cyx&|bukDJ zG2o0EsA_)(uqOY~5&jhpc9+hsWPeb%W=imd{_p#D1{ZDSwa{0O0{s0_s3c6bk*;_! zpXazS6~xT^vPXDTm;6+G38z~!Wu8s!mmk<;xAV3`n1Q`j4<1AJ^JTOIgUEqaA1!e_ z6nLI`V?Vos*4o810?P0UU_7&(8qWA0=vbeC3$0CjF_fF4q{+a=UK&VF2>wE$X8w@P z=uG$?OyN`YXC;R^#rCI*dd6Q{Pdsewp{?nSHhoM|h4zWy_ae7ZqSXOm_3VPa;yX17 zAS9%PWz^GT!h<5cSm2o`yQnrBSJ!TJ4?X(OIvP9IagHJ|X<z`6gpcmD1!`){trg=& zo>B&))<4fT*B0uHXDrc_9d)!?1oiAq#E;^hykZhHMsV?>DCQA-qg|MJlPz*xiXvE} zLH$my3BRUfe3V4|t=Ow%nB)+5Oz$mAfzSxareR!{aE=rzM=&~I7ZBUi9yg_cyTH~6 zHo;4_%n-24mhT4KWuFss-Yu2+aqF~av>*i3@PNxN?#&ErvN2%{!rCvO&YP#{Xx#pn zq{k;K`*tdj*bSf=fjIB1_vbDx{v*Wu-UrFQLXiLXdJvYW?Oh3d=B|kJtll^58UE>S z$Tq&_yks4<!4pLeiQAK59r>ViF>YAwK~+VF9~A2WACar*1u>81@CBv4Gy*B0X<)5~ z=`rIgWP>2yVTzECfn<r>(r7){eJW&Rd~VsQW8l`%O}5p(fmM?UwQa1xPVtc=zvUz3 z9Uc+;RQaujR0uF*hJc+(>9SspIaOZ0y9p!0IPqZs=*|Oi(KYbWb6GA?53?j&YoKwp z32>fC@-j-897^hQXBl&gi_Q7LVUxf3$lrx;kx8_DE=*C#A;D+S-qJ4^2?=`3^I%0= zl;qn_N?x^#^jm8^M}JY{KXM$;OVY&#Q;zw6m)rLAsDW3phlNk;TWCugBN>fInkCzm z=>EQEv&)<aOJNa+RIHc*<obtS-gcnAF8(42XIFL}!3!QdE9)L14X>R}jw}arFz(F4 zAF)<n%sJ8q3-gdncpMCw*B1Ka_L^;m!tTq~hZZhz<Pr$cn=ev<K8t~<4E5te70eOi zvn{}ikqS)9OjSbN+&v=wNmq)#_0m<o8V9uaZSpdwMPbb4kJb>(OM5s0uJrkb5ntis zb!@T|0WLpjerLg$UE)QS5nQq1VFnes3RZJeJFEV-HX9b?z+jR7`HnN?`xWW&yQsnW zZ?z@)o_f1>pCF<pT^!xsoiXf$mQj}s?w$3l>srg648x&E^QsNl!OCm81~tTd1mopO zVS!n*fjYGyC2n+KnY@vuGgn(r@-Vz!#fAME4TtFs$3^>7zhi<SMnB0?w4w-}9b>SN zx2q3|rBW$}i2vAudU(Ld)}?Rzvp-W}YgO}$NEByGwLd<7A)1mtc6<K_9w{mnI3)$y zIDBhjVC}Ydq>x`qUEy!Ia^F6&RM9g&l`C%76=@*Bfeg8KS1Zwl>w}t-8Js!5us;&7 zBezSgiFKvLxTGGH9jEH_8U${@y}x_`QKJX^o!duat=D7c<rCBuARseN-7aH29LDXi zEj_T$6ceqk#O#Vb!wrc0q{oIssY!Z;DJLwI<eSf9Km76d`n%h}2nGmVzi$7_IX)n1 z9hfNIXP=!tcl3GvRdc^5{N%+s`y1X{??QC~2-B`|XAkHrfq13ElFCyJc;Q_WAU#V; zf4pwn`PawXC%)hR^(N=8Y*y|*^zhu|_>+z+-0g7|2i!1x?oT>~`prSeEi-1L-GuGE zKk4=^6x2sQ1oC)n_T%4IPJ9uSJ26l{)xK31axU<64zsLWy9GC|tEH^OH$i5tjj7W) zL0vr(F;3>tJ|Y<^k-C5YzUMXP(M2wfj71*aFO>FW1;CrN*3CVj8065_%{b`YC(~Vy zHcOwij*RM7S`1WbC5#GyHVGomX}uIvY*=S>l0<9OJlK|Sbo0qQCec}7EgJyVvPUQX zTFc-mil)4530$4bt|o$fsp<`%TOwM16^BZRCM-}M?>77w;@&&1scl;u260;uWFsP7 zlp@lliFB1F(u5FtQE8G;Bs8f}RHP#{(lwNjgd!zCs0z|MA(YUh_g+HxUF>_#J?{6O zbHDHV>+@$>3s+X=nscl<#~kAs&tpI6mTQ4c@CMVTpx^0UU0Za0G&ZF0$LV}#Q2#iV zXZc&cfnjB_j^*^}gl2hf7Lv`v?$l+V(uy7wYC~(vF0uDKnyYV}t;FE`u4;v<KZ<ET z9Ul~o{f91~7pTm|e<R$eh|_R(&Oh7a7e$_HJdIf)K$$%D&-0};)!H5QUla?P$4_KM zn8!A)FelqiAJ0)O##UGZG?9w85M#^$@uTlMw$<XkBZ(D&v+Ly|cF}{wDKW;}w%~cu zm(4A__%4PEIl=I48mN3d!wx4o8BQ!z4-I3_O*veI@Dy9+41v}5Cyg_W4^Wwa=Zf4G z&pS*sY(p=eqfET@<bn-=hyU9;sm>O!hso2z@Tu&iyrx*}61FyJpA(m|jDUhfz&jjv z`U(U^eoh3Ed%e0XZL73|kWpz69Tj~#YUpKUhCp;=ND9F~VHGw(&lIn?_LV9o_VOEc zc;?US7-~=KGw7_(5iit-BQ)QeUrzOT;mURoQ$6P@#Uj>to_(k&22{q}2^uWFH&WFV z0gh=NaQ_kZuIuYcE=Oeo#*5o}Mpeb-bnUTqNs*=(+LBXX1ks*N#n{W3=6czrRChd| zySO69_m~;Oif&_nQmmjSi%IQ%(Cf|)zSFwTPt1o72%Nr7(cdLzG#1TA%6{Uio84l& zuiWGk_~`)_>H~%_m7q4?`vOeAfytIhjx%WW%GLuZQj5KYBTCAF3S)W1TsfsMxB?J; zv%Tj+cF{C>l|V3H$^Gi-X`wz;hMmv!*~cl@l3~@>0G(HC)6aTFZ}vTxeSmeVjNUZn zZ5lDdJFir+2k%&>VST_8^0PWz^cMwAj#0LR3gLjK6xQ?i6376XCK~93EXhz%64Oi@ zixWF-Al0n>+2+_q+4fA|Y(IS^fP{I%7ojlc<bYE5M00a;ZMVf{fCfMO0pehg$<FZ| znW^Iv74RQ_dY%VSVEG3-T;sbo&(#&fx`Fncy~q9Aa*XDi`ktW;*24=((IBM}N}^$5 zw@k4u56W3A1y%Uc74MzSisg;YUkG8knEq_oSk6n>08rj&4GkU#-j<iG<M*7oOlCcO z1hQE#${BRRaK@`GGQLR-oeU&Toj5TADNw?_FLpji53t<lx0&<om}l{uAguX7J~-cU zMut1kzBRTFk9YYF_5C;-`+)V99)07C0R#&Y2<cI<zd<2e+-BVGCet2a1fVn9;{DZ4 z;-cKULe*#HgO5aEb5$#OlJl!X9_wk#M3_6o_s^dgYoxs-k<K@zTt#(8w{n?c8Zm11 z2OE6h9)D(FDrV4hKdttn>DY&r$(opP`?M8zOkhw_M3|l=@d_@wu5rgmrtKYs%NA@@ zP9gX=Ta*u&b8Z}QoJ|fW9;G!?e_s3$V0`qes?OM0=d#wb60H4ZZ9cQZ%m8xr$C5)2 zO}@Mm>h6JtlDxce^`;|m{K9ramTOAx;4IcBU{bPf+rTLM$zihVcmE(^z01pMC)o0} zYf~r`W8D_@T_O}*m+LO6l=raDV17nxMkbmW^EtMSu=?TZk%d&B9EV-9oo6Rlt=4qh zfqtMZPR78>Z9d-hlKB`~=+k`8ED-*4NWRPc-XA%!%Yt=}NBTZZ%P!}W8UR~nn+`WL zs_y9|d^t7^o9yQ{19<KFTAwsLA?g@UzE4~XAsVa(UXDeggN#~q^4u)(hIrq&>Bx)e z0=oLMGqr!@ttx+d3S<~5B>CJ<a+1?Gj+Dad1GMiV3ZwuHi8t_zI$|>xT|%3A_O8Cx zP&NoG84U76R|zSM$+5*yzV1@c0^IQkqe)+=RO8rEg^t)R^|+KX_G@ox6p-kGMGy_| zdbrV}Q-}}yW+MgxnG!xr!@^<1`_~0uc)2}TOB*MZi8_mF;Z2--+{8w&81v|UplNU2 zeB3ZUThIs(&B?*8Xe*A=CXGb0kT1{ufM2@CH0C&f_M5YEs=7QKIh(p#P==FLHw=k4 z<nr-<8H#OP2^`Yi8P>Vs?&ve@h@%o*pdXE}6$%+54ljx!Kz(&Jar%5{A5mX$a}MTw z)y<DUc?jjlziKf62Hy+zug9u^Pji;ICk?x4cu?KuXB>^-5ON@aM+C$Y2!w}eaYdqN zSw7$RoY0)}US4Hh50>r5N4-Q{*R)E9oW!;iMz5JnTfLzXYISSfneRt6VuC}_ZxFlH z0CB{4GVV|c&#t-3Q5<~2zpPtql3TRj`DWNVwTq)rZrJK2WIHAgL>+FhEo)1!$2wa1 z|9PhVHxsqA2KDCMh(w*8h*;(~SN{K=vf=;RludIga}K!201ow3=3JcO1lnV4JK+W2 zxn2+9d92Ns=PV_g>;kncf4`DALcH5&{;Wuq>p&RXiqrEQ>oc!#R3keY4!d4N+IA-= z+ia0UdIb0dp22O69_aU3nk>xd6vCSmEvtbY6?<rCPQ?e0iKPgqcv$!Rg8Rvy!zZwQ zv><lazP5?gOWcIF{mR6hl*Jjhct$W=&*g!KlaD?7z_@YMo}J<E<l(|C(Rd}UvWl?` z1(y$B=Rn>BCw1|)+hy|y7N$-v3CW#ICETncUnYmb+C<NivaY8x#p;`lB*J$EMD_PG zCfk<zi16Dll=yo9R_&6mjewSb^I-tfo}cRh)p3PYqfL<*s)O^I)iXuxzVd^8-L51Z zp_)1;lg*&Fn^6nb5>G^jM8CoRaXGxvrcNxqgYWe6+|$c7XJf-eza|^{effNpaiE$F z58KsT=j<D24}b$Y?9S;MiFd*sw$vLCgSwcLC*1+Rx9Ijsv_tyjam>;A9qA^{&q~9n zBRfrQmZM%*+g%1oSDYkg-0^P@@NX|yqJjR0yTkwe^^8XG-XOuqkR`E#|M}GZ|M971 zte*7GnD^Twxjex4%yi}+&5x}C`||gzRwhyYBa=fMU2B5G$G<2{7pX6YEN@5h<KT4H zgtWT82jwmGFw6$MVL0X$4R!~*ziE#N5j`etA!M~eIFYzj*cIVzrUh)T8y;wVijP!n z5*K7TR=bnkHv(1*9o!QjM#0D!2sLM`n{LjlLU$8OYAG{sf1#u9ZugTIc&4eH(&TZ} zI{yjk-gxri+v5sggrxrRSmFQ1WwmY^FaH|t>$MKX;APwhPr5yHPO2ZQ0%cHJWj#O3 zj1=6)DgxhfF7TgxTmVKUYF*G$y?l6^>ChcuVs5=77|)o$OhDjStDE@4%;1wfp~0OO zca+W2%KNUeSRFPg{nn$8;RyQw8r@6!o6?Q@r0*xAN>k!oO_KUD>k}PbU`T=ML35^i zh)`>yiyH_}<F=&%Scx`P5wH>vc{U0#t90<uUuLEJPqSKM4>+|d#73e}xcy((>;Cil zurR6l6o$pBLl4IDGkm9(_?Id2pJeY%ZJ+<^0b1;s#yl;hu?9nXn!}j){%gh>?4*Qz zMpaGT_fb#`U-ZkI0flmF*4j(S95Tu27EUA`IccY{IXU$O|F{zE`7RjVg1dT@jdi>I zPr~s<&VhT;W>Kd6ICi=9rZRcyLFn!645$cVi7>2isBeM5)Kk*JM+#BMM9Up8Y~CaP zR)%4C-ZFW1s4aV}b1v6ga+5R1c+mhC!2OsyRzE_rFF9sZM(R^$+Mf1I-g?FKA!E!? zcCP1&jBS>aR1HysmXSwzHI(^NxHV`n0>U>UXH8j`4yJj1pV>OhC38o8<Gu70|4jMq z<FG~D%{@3)BA^z*uT04X=itDR^2BfkQVGXmyl#{E@x_{R`k7(vA@3SlKWQ;Lze{b! z4xguTt@pycYD~bhdw(Lh`mUvDmNi_jddl+2>zlmDR-XarMTl*7+N1P2F+K_QJab#W zCuG!{v9&>=+8IDbJxj+dVtS$;(HmC31v*R&7(S8opKBY0RA4G^XcLKG@%B9QbFStc z7%F2$+&gqH0FX0S9iI_80tv7psyEIunF8bir|to;PP*d3teC!5q`R(3GsWfH&3>tr zPB(7wd_PWn#!9c>mU~oUGJ4;KOX<=GeoYRlEh~r9VBTlSHcw?u`*Ba;(|lrwl=9e| z2tB<wX!Q_C1HZ8V9JvD>YKI#rNa?j2`+_>dvM+;}xtQ1zL&59g+Vp;6CqFM^aFK61 zlKFY%T*GG+!rn%-weJy4gd)7O9+hhyUM77X#zRoByNU(|YI`U+D9CWX-t|hCL~#%J zPPYar+btf`<f+j%(l7EdjZozNCG-m>rLf_W1d#!Q9zDy!=co#zDTqlmovnuD!N~kA z><o$qX;~d@0hi21KHT{x`N3UjimKP@c;Xn;F(!8X%S4yvqVPhgiwAgfiLrlZaMwLL zH^6zd1Jl=O)-jM=0228#;c|Mghn&CK`?%Zk6$Tj>ouV|Pw<u{sWC9V<bZ43A2J)W$ zu*)G-X$_sS%Rpn68R2)Y{#T|L`@a&(Xgpr^kXLtbfIe(AY~hm?iz^&6@pd7dVPRx1 zWOoLuVQ}I(sSN!v3M#BTF2Y_SrJ3j>m8XR_>hnl^MAq8N46qE2sw0c|c9IA>kt_OY zimD3;J~dP!!)5Ah*;{VfPssRFW;cVwns1j8J;uQHA~rDT5kD#KeL>>VdVJozrvQFB zbA>Uqsf?gvMFTIV!sJ<hcC%~M&>69o0YWWPuq{O?LrrcA!W9t)#ro2(o~x$lk%*{x zM*155@xF=pnwKLg#qG=6U@gfjp6HYrNa1J<j4TzF!dJ4Kdi!|}1SRD9upMB}#dtk9 zuhGM9P%k)*GQ<~{W6i5uVd`hQ;`z{Su<^it^38TcY6U=L7bRE$aK4RN@7B7Wv~Lm8 zjQ#~~`N>@Nn?&T%@64O_KY_AzW@-O>LKBt2fI8C$ch5!Y$`RL0eh(x%Au?}SMFsL` z&wPwXPfuTsl#7?WMOR#yBH+kJy8+Jbc3R<0!u;%Wvha(AYl2NAK-U91BvN*ZZ-+G# z)$H>n`daJHrirSqTpnGS%^_BFsV}U#t+6f^$-O^8b<QEW1i00?Ia|0#n%ASe9`&FH zFr17dQjQq|u0)ckV5K8eD(WN}`-`I23Cd#)+8O_RAl{t*V0zE8M9ZcykWSQgxPu>6 zqHu1AB&-Kl&vGiwS&rMq<1yS@*HohKGUa0C9*uR#7^Rx$Yt0zls;-|V{p=^?m<4=* zW+wLZWw~F&ui|3O5|!?HR}J+FIjgQt!vybqnrTvo(sjK!A4)WnDLq)zoA0p3l++6p zu%1Ic?vRs!*uA-ZD>@fS<c?Ue?$N({3&>3Z_!GEQshhKu;><$z&l-m=86rt74HMJh zx`iK_Ei_P@pLMYr$kd&+29zw=(P^K|uMbkhHUwnIIacOfCRgfUaX5u|_iZoL-1My4 z5)--WP3eVJ+O1V1DWk3_?qWKV`wHcHBgfWO`MB#NEq`>biMn`b+V*0CE`^7#+RMK9 z5)IEak{NPK366A&2ZO;dN8|73n|fK5G+)q4x%7|(zAM`lnNBXhn7{qy(U<o?&{quw zzzYC@V+>{nY_PXXN~P!^#QMa1i|mgvVbBro*^!VjbJnPI{$40}c#F?rK``kDY%gE- z`Zo%ibrRfmm!RqFoINIYIcdw{S?*I53SMpQxB2D7gPq2jOQ)sFII(qbFwvS78;J*l zMqX2XvLAa>U$CgZ5`C=mi{g;lV_>uacbI4Kiz0t(Yqz@0iF71VRdrO;&hm+Zlh63Y zif}or-BgAYE+}-zy7J*y!|r~S7*z@Ju=lDPr)5!#fSe9McKkG=eA9P514pWXhnX!@ zm&YJy9HMz~y!~?^t3UAb({5I0LDe15=;lau0l_4|-}OnpK|#lDQDWu|8<lh3F<e+B zn2>~FCNJ^>yktEMbluYCE3d+Bmhr2Ww$CdIyd%mGftT6Jh7J8jmpU$SLHM~G8Kvbc z>X3nu@@V>;mO~~Sz=F@#J`Fd5A*JuBsC3<@_#r&O_91u|qQkjRIxC=kUVa&k#`TN{ zEF|RH;n}0oUS&BFL?w)|on-sy;A++p|5We#gqBt3L9<<Z)0Jb|fQXXP#L`1E-h|YW z5S_6{zWg_RZ6ZuZ%7g(Bw|Q6`3^}b&u|#r;OE9zGNP1hg1)+Ym0Drc!$djhi5usL= zrolefyv$__I0Qz(_+nDO{(08h;<w~j$?wn<ivNvY3s~`Zg3n%H`HQy#2*G}&p*qXq z@HEC~uJd|-z)UsM+9wJ4tNTM7+lYX6poF*U!(7Svnx?I_Co8oq>`Hu6&gpOcw%wJ^ z)wD3Y*kP3Phg)QTr_lylT%L~$U^8GH1*#v0z0OkBxt`Tp!yX6Ho}j@3dfA{Unl!Oo z@d@*3fMQgqdTyJiF&SXHZCq=h?s0A$GLfR%mtipx;z)HAZgTe!!`eOfW!=^ZS%eFM zUJMxZ&i9WghV2HNyJSrtu67H|(9b<(`RdFF$L0K-iM9|6<ri7@#@oAJ-<M$nlxC;m zy=~v5Gl`RT7GJgH`Z=t`V5KhwkrW(Mnbb#1PgY~MtjUsK=G39Jb;0lfH!$dNfi7{R z<-^3gsmWgy_Va>fF9y))655Vqd4NLF81ljvuuw}QF|ifbk0q;1D*^dXBhA%Y$raec zL=E>K^`pmqo_hYF@3m3g-_Z~AluU*F=nPa;iU>df5TM&80VuZVeyaygx)y1FbhB!( zS76Nsg^toM3a&o+h9+O17R##-7Y>Q69PyXV!R~kv$IragF~tbZ#2$g+)-p^d-La1< zS)~MgqFsIhY|j??zbG2AXP#A>)TNTzG(^8wEG_P>2t~*<_PxN$RR@Hu2p!S7G}an+ z)E;RY4Ch!y_ScuWr_D7)4NG@rtER31EECV&yDYOrkZK;nB9o}x!ZpR~lP=kPPWqlc z118<QhLsl?EBJ(Vitqt&>bn<udPTr`^sbTBbFGBZRgrsvZYH?lm&rz1_D#PiY)!}L zR`<Eh58O-pwu{3BWUU@FSI;}yb-s+QZ>-1TlHE!TE1;#|uBv`1sIx9?$E|AC);*#% zAIzHPQjBEr`_kv~rQ}OiPgd12->zST=g{@@ccUCc3s=t}6DG=Kli&8N5@k~tK=OdL zZ^v`<Cau2K%}pep0d<!)gymXCjE2ySQ++;)?>zT!-2Z1L>g?-%65_;*lIyHL!xR*e zv~Y%7;{Mw!^P^UFBOUxN21s`KUmOntBjw&-X<@SGs^@Z(GEbc^x13t&Q`Kezhs4MG zRuT>N5nWxB+;9b@lKz@(TwbSSM9lXgZ3b|Gz2M;N3?ri~@(b3izCIw4)MO#uW+N>d zYn#6=1rZ66NJ@guCbpJh;>W_w*8(F$#6z(`*!ZiYSj$(he-=O7;P(ogE3b?$cSHe^ zQ%&2N8BIo7am+LD$C|{2_G;J;*k_J<nTrd2{zK(0xG12M%qz_n!r_MA3y%$kYO8V- z==JvXXi}}L#rIWhmu!5)vTA<V70=WGTUTth&gT2f9q{7ihEez4_M}KS+oc++&sCxz zK@)0GvmUfiuJ~JT#uOM~p*bn-ZX6@iihsZXQV|G`3R%L_!&Y{}ZJ0pNj$gM@&HY6| z-nkB!-T01kp*CHz&n$BuwL=z-lVw|noZLt;sf9$fAv}b2J8>aR)r~QyuhSG$6MEA{ zFOJs%#E~HiVAUR{EL3^wt|}paGSek>a)blvAHe0druatn4^b(#`!~c@#HlF$UdPjr zG&gf@mi%>GMyx}2H7}WEwX5P!wgApw6iLJIz7Ge{vDU|v*LTRtYI9N%MgYzfCyv3s zQ`t%CsY-hy95pPM>m#ROpo_@qq2#pfS(Wo(;&9)IkCP+JU0b1(Z)3lh?VW6$h$)BK zMAxdo3f>AZ$!KLf`N4iw8Dlfat!wbY>|9bc1o8+2ak^?6Sh&4iWFXEbDlSM5?v+Z- zAcaNd&8|T>mDNI<B<!D23nCJW5kkF`x5cZV>Ky}|f1cnA<?NMCmO!mtjSd0mDv$bu zIT~5<3_x-z&)N>YatGJiCSNeLb=Q0Y@u8Pe7!KfK46jSyJa&5<Caiw4^fG5C{ZQ*3 zRvHy9P@yp|l5(!WNE5{NB{5>8(kz*q{gWFQ%LycT?|<GpYS9bmN)j*abfqjwCfl0z z4?<bx3T*mJlcd0*Me+Xn-<B27@pW?S*!MGF`QOuH@GIB?rqr4d--sr-s=<gP&I7sd zq*YX+f~UB9^U-Xk1DG1A*&5PDlI{r*PrW)KsLpJzzQ#x68<Od9Lh6V3atpy4Zk1%B z9vkY*V2BM5F+Ad!2?qMjYKb%>MCx_Jq<IT>8B>R4m%HO9-%7?eR5^Eq6!+Kf{*{jV zMe&b(Tvr#iOL|R8$>ex03cNF36&&0soG$T1&l2mnv%Px>dt2lRp_wEba#Mqarj<DD zn@mW|Sr*6!@u%fHL(CH>Z^Cg_ftW<qdW(^5iH&T(R}Pm-gY%2l(uT*ap7j}(<%d<& zaH<OOFFuQn?le%X{}hdJvrgW@sj5bxtm_Q>Nkoko@x9@T69ja`)V<wTqjgaJybTef zNhe8?`|9)xUoiqJdahE*+RB*)5ES*18rybB%gpZCd{U3AkWuJNf)_gAXDZBJ=Af@8 z)`>BKVTs$u4S;z1LX?+LILEV$y%6U_oW5SI)An)a@bf-g*BkaiBJr-ejK0=PRRpxc zy5>BR{3Y43UxBkOijGLHCspvd*AR_`@WNm<6$OaIPOJ6IXuX`og#?@|7pq?OI0i>Y zbAVMh--C}N_kV_uur5d`<7kCbtu%!-$A%nctZS|`=0OB7eC`E}Fi$@Pl!I*EsPEu# z14%vRokZKU{c4{sVLR7Q*oKa)rj3(hj+r^2-}J|9|Bd4J1G(ck;S<4cqn*6C-Or?m zu)c#e4Go>)ETn)vNERv5uY?4vvK5UbZd*=GtcO*XrNY-q17jZ0r&0PttKe&Gxvt8C zNCiw(L~|GEfDUSObW3jXPGkPaIMJ-77S;!r5|nC<3=AT|+z=wBNj{67f~hOMdNFXa za&akn$%`&|5NCuwB51CvjrA;wc+58%4bS%wa0*xk+h~6%cL2{fRQ=Tfx=~|dX6lM8 z(c1=&$Ryq-%m%I!!n33fvVs>j48gZsl}L@S`qg~{SoNUO5#5)HD#N8pzH72xb|ufd zMN%}g(t3PjOm%ve^uPi#D~^fv0q=H^d~cJ0_mF|lL4iahM+!-mcJ*sKs_?8wEjUX$ zIWi~Siss+Pr>CK}jkcR|h-|_^)AOF{9&o9v0&Qcn&I27By5ch?mlnAl>5(TBtdYL{ z8hjsM_SLmOEbEfnN#6^lYvDsnvbPXW2LS_q^`!Vbm6`X|rXJ`zP*<dP3>=)uWSFqM zP+lh}h7zh}QPa~t{zXCc;!*I1TRc!}@P8*~|Cy`3^;fd?@5xz~lYLPl+~;mL73Ge` zsMyB~G^q%_0m|#VIfESqpNE`~y_F`=3IlV1;k7!>6|B=(q+uGGKMRvaie~+Mg@6E; z!Y`9ANRQg1_w0w;P9}&(eT%Zg5<KN?mJPPbGjlN86`&TZj_*yp%Sc=4_^qb^zo^Y? zU}|SRcae+o%(?rg<Y=e9CYAiml*zC#X5L|-b57^e^8;ZkXzk6ZRnwv!7#NC4U`Cwi zaH=XUk_*OVILsm>JA^A-5h+P4*WqDf2YyAzLA?X&<@oKS8B5W1g`LKU2ak5P+A448 z?w7#a;ObWM^2hb24xxs!_w=)oU|m%+)}3DzB{{EvEmLcO{%(rGK%B)xY(w?E?|wEX zzM@}8^(PMKtw<VWfG8ncV&v8oh?}hig20Q1aY=J_e>v8>;Uj3};KP;81ZUs^<f}IL zCeR4>rNmZDt)0@F*nQTP02k6P3Wjn0rD0{`!K^IIb1v(9(At5MjAeZY_K)Wo*5!>& zqVMqb2R_U>;Y(Jxee(E@i^2zR&yFMf8xrU55_Zh2IDd3#yIJ=6vPVL8R8?DVYpE)G zln+~UDVqpc{;*OFkhBc-F-$hBZ7mh7R+N`}5qm6rl>TaYyGDty#)@)J<yYdE+FIGr z@YPoB{9qZCrM)FuHtqZJOhSG_#kD?tofc~^w<J8vNS!O1@t!=YCqF12#q^=?DKm92 zg3^eJ8GbQ4D2=a6Pmn7V0H%}ito=9!0StB|_=1!0P`lchkpSV6pd|OwQ6`<Fpz1_O zXXJF|^IsGZ<9x@K=@YC&d0w?{QcLL^OZt7PkA6{{^k!Jroz>OHRa>S{3C7js!5=5I zd3xv9&S<Li*vxl3SF4hkXDs)(kJGHZa3ZTt@a&InQ?*N)w(>nkw^>A;Bm(YD&BFYr zI4sKRNv%vW35#;0Eg*akNc^toX!QKrVK##Pt%4eMM_>Ju)xLFqKAU0Ogtl)=dPhm^ zK{9W-Jm`gXeGeG`(|dLnP1yEbWf;ajPpFQ@PQ-^cJ=2SvfpN_+RWs6qFxRa=M67kg zQxu%8r;vo@K0|pj3JEZ6leusWK>^DdKjLK<K2^U+SO%;L-P#9M`|?iB)M)1@;D>`! zo+7`C(j%)vx(R8Kfkqt?7|#MLv3kK?y=u%IA3`=gaXHLZCL~YbSkdjFguIm&iG#bW zC}ByU9>_6=2;M7#%LSPSWr2NKB;YR3W}+-U|DxEs^Imn;6)w2}Q0Au-=6>uGuai(b z&MF3K)&--o#3#KfO=`h2i7jz!V9@?rhL_b#_p9L+66huVpO6^`%hmo=|J3BozP%oL z|Llg9E@nfs<jK-i;X+}L%k_RI(d-8vR%S!Q<vl{8BDZY(v23!YosZ|+851j2VkX*R z_T6~@m{Lgvd{a-YNj<Ft?;<cEp7t3}-(gq)%f32UW}Zh5Wp?(&%nR}huto*F;!!VL z*#Us#DUDtrLdNY*0X=UmvBtrSN5k=I*|I60nR#Nx0-4}<G=-i3yuHzdO>E#UUm|@T z#0v;a+~B>nt0Hj`y2|HXn(m%(%zeg6&@9d*dZZ-RYNK-c7X_AZ#7zlVnHUj<%oVNW z*hj|yqBurBst&)MmZi4ib!-bVa>aIk-nU**vY+%y5{>OJ13%8Qoqc-Ph9#>Pbe$Vt zr0pJF5rNOC`w~m{c<t-n$9NW@8XIj}$WfQvCb3TQL=$C=SLFg;va+6qVPwl`t9dEr z2vu!Z)v*eK<inUAGkPnuqeG)FV25#2Q^}ziwBhRjt;Lt9nujd;Lyc>@8<e;v4+irO z6oW9&eTK{jM7IlR?YNp|d~5S|MZXQhjr`=o;oct(-A=fjgsY5JcJwnuu2ptiDez5T zo__A%U#ryZ8+LAeB`wR%x)e%%HvagEzz1PDqW_~9ZdmO#Z3eC#1=yrwkrr`uU!-TL z;HV0veMqpnRlJ+tla0?;aRW0liif7%4oG6kk`^_eN8#Lek(j`11t+;=&yN5w!_CtS zPSyj=!Kd5YfaEL^IbI;V;FLJ%l1AXF`NPUAk=TBi(z3w@^?&}}2{m8qqMP^)s;IqV zAtjqInD@tyCt~@8HgVowKX5|XJtK6&DK`j{;JMWxDSA5gNR+Ca-H`lhlv12ITX+I1 z#kV{99$A4JR_XVAO%4;bt!g|VU*MS`krDK560!Va7*z1s7}xHH4_J1u)Gp%3Cv65f zt(R<QO;)8ZGQJE)WdkIW*sm#rYDkQm49&$^cC(Fl{Vg6bGxe)|;B3EMCMgg9*;GJH zll!1_jCkBBr?P6`YjlI<{HyZzZaJ465Y(MEW(q(2MFRYFfmS?3vRgr{9gd6T669wd zLE^0tQnFarzV(C?M)KR>)<F+lHdbO(QITPvo>L?u$M2et2NeGaN5FY6@u;i;;b8+y zBSE9Mu1bloW}07Fy=I}1Z4Zt#mY0hanW{1Q6)HMK20BMIfHr|oK7$}oi|V_svANxe zAXJEdSIsI(jS1~}QThY^QD0B*e)H2n$zv?@4>hF6y7JQNr-2mTfZhTq2u?G>fN(BF z^I70GCF?*{6|Xr|Dof{Zy(bssEUj+<8F<t$n0pWrj5l_cYz~by5<MygrOFZ6ry*sw z%~VEr*CLYrEA>GuN{psD+Mh=*GMea=U6in{LdvklbPtHaKj+L?y#^K*0~d+Z*caB( zF@;4xcB`XQ+{}rs0-`Wv8uaDe@dwWN$vHiTQJGFYksTo7wimT$>kpWh0)DXx^)@MI zdfh+@U&Xy<_d^h~CpR7%dPyGIasBOp25)a`$OMPNL(vrdVx&6+pw&cBIOTL`y0DlP z;(($+KOOkXtUUK4S08bR@Qwb(OUkh)9!pm;<5mogvG~-J+_zs|O-=~9vi=|pZ)M#~ z(h1S_z>>uSTt;?19N6OhgF`P$By!&Efqz}3cE^4ln-_Tk=!|5bZQ7d}8w})rzKPy) zJ_rPSvi|&I-_NDrLgEvOvOX4bvJwBR_gRV*xl#&WDbQA~l{IvjnlI_c)Vj~j9(UP4 zijiR&%z4eyF8-+b(}Jz9aU&PL6To~iRD-oaUO?L`I7j@%*0b_F&cY7F7KNYoFB~d^ zPCUW{a^q=Kj<MbrCpG6^MpY-T=3%E80t5t?H$^3r=pJS!R0^QNJs7ELTn#$0JtLS; zTJU>Ri>rKW*A62yb4Zbl?kUN&<@6ZwFHW1!8|+kJINSVu-3RoTbjnbm?t09w@;uzG z{BgP7(qkv`q?K{@v~%X88DxCB;n4OHBkbcaKrqRgXSHw7Crd_3wa$1t4Ih_vn}M?z zwgW~S*ep|uK^~Ej<2r+%@-4}g9F{v9a6nkHMgWC63;1I<mNd`C)GEH*EhY=k*ZM6I zl{FYRSh*AIZ-J`8y4eA~_m#N2>_*DsUnc54F%O=_8sS0IFp4Y8Q;vF>POTXr4xJ}7 zf8Ia!>rQd$K)?`i>L`Hd;eQ7ZPo2EK2NdD|cSJGOG{=F<Izt84KHchtL2q{fvG{54 z<>7?Qd<Oo-OVR8^2wC8n&iymW>`tMQ5qJZLHM*5D5WtF_z9;{BX>R;U#ohA%peMGQ z;T20Y&nmO^6}06&(%)uM`2NVf#lSK^Gf~|SS98LVxEgF@==j=iMHS^#kxg3C9x9Se z?Q#r_u8oJKj_M*2!pO7C@Sh9*K;U3|i(rx{X!2y+_fSIyar=|v`Z<ujy$h;40+)OB z=JgOE-f<hP%-Q`+G8e#B14v~93~s<1eg){Jw|PQW9v|)3le$LAkjGnCFfEUk`-@1E z6acOQR!rPqp*V_CNQRAp>uePsz_^q0EN8U%n<*YyM--|>m(dACXnDxozw-b9I|BW! zh|rq?FiX9X2zU6@*?;8hNH^t^SD@-a@sIm6Z9C@B=;{jF>YDGbpYn7AVHPT&v;Ns0 zV8%HcP4VXRT&;HLGPj3L$|R(v=xV8)K`Ayp*)tx-llF3~ClWSwd1fIpmG&)gXSNmb zDV_KH1IFOzD2}c9+HxuijTWh&%;zr1_I~Fn=t@z@J#O<69hb%Z%}H5EPwS>@-Wn<s zo-w}#>SN*pA_J=ibt$jU2HhLJ>c9re>1zVTA@t(TvcrSlaLXtBi;KC-^ml!RKpLap zsF~Z3o+|!}0m6Qr0$|GfZ|hK|_y%k_)F@y72^>+<ygs#<zyAMpH2!m#oP(ml{9ig0 zT|rbonHKMn;J7^iNj&QpdsBcNel``FmqzeM`j?WSndSPL>kEsY9SbX`pwR&NH+JvF z1gE*orqy0RPYVy;sKw>8rte4%&O^KrQ!_H%`SdI+0yNz)k(AD#f$=zrbXj4uCK4Uh z{49zyWhK1tq0vkLEPYIf+#g?ybMiQ$?sF;beP2ym$0B1ZlEO>m)`X8vpNxDu(A&r= z7jpUF1kH#KIQSX+nzj@EI891wEBO~iQHfq-EgW5OR=ia)N_|l@Mv^4TCr^Y%JZKmr z>k#)eTq+4Y0!NQOR-W%gZQ8sfD>4`@MsyKZj*DG<>b}ea8n}gzI!c(^1S0OK6CVv6 z^L|r0Z_3~9pZsA|SWYyf4(I%}W)=_ZsXQvQ60y1q{t}t<F+*7;D@7e@pBfZuVq{+t zIL5;0|2+8dzy$Fo^DO&6@cg&L&KE1Hq5C{LII=|!Dz+EcTnf7l%rT>EGGia>*)n}_ zHG-|gx>v8E<jV+uJ@{b6ABgo>N{%x(IT3VmKv|0aThoK277u_w@Xz4>0Et=~Kc!z3 z0=or^vv&>pBPHNAzLZB;QZ43FWA(|B(T*)yoagwI8tLnOX1bKl2Y?v;Dc<~)^5+Z- zC9wEXaj^gv(o932ck)CD9l3MlWCtjD-?)3f{};t=&VRA<|0BrwSClc{{&+SWpb+XF zc=BZQ_FS3y{7-BqI^ucK#VHN$_|Q*=_<W!ON3KU(X;g{t?uQ&=iCJNN!)T><?($Bd z`TRmsgFn(Vcq9@cq`mzag5eUq1?at|CQXmL<h-w@qj33)M$?xD=Y&#Ue<3ofS}(EX z5~n<EY>A`3pdQ@(hbKcKJuI=w+-8QE`mqT!Ibfcwvw|c0D->&GM}aLrB`lgQV&VeX z9AVoD=*~JkIDB?SO{#OwP~=Wnc&G~MLyTe9t$0VJm?gWrGkxTa9!#v&ScZz%Gd)gg z$M+}A^kCa2g9Gk(OsY4%7OqjdXxMgw%GK$vufNnEv~b}!JrA_C8J>sU;vNbrj8IWS z`b5+0KD&JPB#XA=gd0BfHBX~elh>gYrd>R@LSDY4{fpwN-Y*LM`n7Z_m)fVME_=wR zg9J{$qhAzNCtIMM^f7M^D~rSC;PB@|%$32~F~^pY@hD9{g71e_i_N61;eABPZfY2h zi+vkOf(5=J(GlM$hfoj?Vew=lv)Z<ctk|~c)A3KpE4gX=^>{`&40nLD*9VkYTrU=b zeb+uGmbLfO5w;md_!sj<ZJAPJF&E-32KjoJ!`t>bIu@Q8r(SLDlax$o74UUBOB0wB zbL#<(mX=gBj-K@|Hq}m*zS?puRHs${r1P>qT=Sxfkehr~!nlB0*}Jb_c}-#2XzBTE z61mELRjsu4ZG4#OYX11JG>3zbZ?s$Jf`<00y0}S})5FKx&bM@i*dwf!9Nt+Q6__78 z?N2C<S0B1%N@^w5j`$pN4Ne9wCam2vdA59o-+KAb5SBrVuAIo15gU+^m|)A<h%CDl z47X^k5qN06uvy>#GUD!dM$NOUX|QyoN~f7T{S|Ek(5QSK!u<B$E1q!mf>%DJiwmBm zf(rXWC2Uq6nFU!{j}?amo}(W1d~|awE8ezNnXRupjN&f1XFfo#o~>THHRdv^7Mfq9 z2kO>6E9)O08m-FiR9C$v#Uv7vzsDcBjD{Dgn3PPW8n#i#tMrCvggj(0FGk46g2H3l z6JVQ1cW<L|%zJlihw^%Tr4dB=wVGKD(sAZk2L~q6XTbbX|B@!?X^gaK7NNTb*9GGf zJXCu;v+ZxZ6e~v0I;yN!fF^E{*{cSdGA=v_Xy;~3cNRC!v-G+$mnN~S;UB`I@<8Qj z!lQ;t&K;*!FOqHg!2rwO&aUI9&3;knb)CeWip^zAM6Jaf8<8`h%{Kn}UiWU$P)MK= zOFU=Td5TGS^#tGIGDF(N-Yb0?cC~s%n24<(g^vn%bTzOec-8q=d5GHs08hyOo6wl8 z8Cg6pLEJZxh2@ssf33}@(ArZn1CM-ZYBLM&C4uqQM!r$l9+$7C2c+&D$Kxn^v(iJZ zoQBTFVkVBq`T1F8AjSM5gUGvHn7snD0>rxVBtN%O5RP5|_ibYd@l3t&y|$Br?Nq^; z-J*T)u+FM62|M&7tS96Ix0^1xVaYcodytZfi*?N)D6m(bTpAj$4P2Hy=DOBmkXLom zaCDrhqJE)+z{N!)&UGV@i;IN?IFu-Jsu(b}UzxWz6kSOk8{k7fX|8}9=#8@t3Ghg1 zYgNE`t=_Ld_P&HQT`l^~Z5O02wrpvK$G%sUwY)y*TTP7&w~`Xsfcvb&tyiH1?j)na zHj+j9)TEuh;<<PtZI$|LJfy#0Epdk@WJ%X0k50w6CN;-wF<}E=n(+L@VNdB?q{!rZ zO-N*_Y6ZnN3CXWOjU(V!0_q9>bpU5R_o}03WcYHwL!#)w2cF_uu^v5KbBsY-RA5>* z$~X0A8d1g6miLbik)XeXUEi(d+hknGEj<(^Z_Rs2pLcbyAsMIp_YqT*jf7FCs7q2n z$<WKjy+b3sJec;@XvAJWTNa2{h)^+Vtv<`<NVg4Uz}tGMMvrr<tgFbwJ1Yk&VqIOf zARL;e#Q-&*+U?}H&@}n&$h&(|iU$SY)*bmCWJOiHeH#bT58pVZ=W;I+hAVbXE&HIZ z<XD|PvoMvQB7WzqcRq#k%{Rb`!)*PJKlZp`i_v~Fi<gg6l`IP~qURTr{TA~BZ8jDT zjo|M_L>p6SM@aDMiQdXWKwv0IHM;->5w@tY=Y!JiX&jc?1GIB`Oka*x74uX25Y@ci z*|iHx?@GXZk+YG;18YbW3<p9Yw++NsoaY`)tTA-olW6^ee5ua>BCS7M#R*;s@=>x^ zU-m6nC}i%kDN7<&8uGgJ%9_nw(i6<-37FKp?t^87!<P$G*TF5f4j3XHq1C_a=p>^} z7k9r_6{kzd<f0DK-&(LPH&VXN?YIf-0sb2~2b8MbOfzyNUv4Ws@Fruil^W@v7Whq< z6_HwAd}xMhi@!$A{vIJLW$cQ$uoKj>Kcc$I9L4tz;pqmA@J4wav@dmj!A?WqGhe^8 z3l-Org<PU=zOfBY`YhVV{K4frbf_ewCnh~Zq2rg18Fl}N9xwB1>XE0Xlgr)7`$b`C zIa9MpjY}{(5Nk~3ev>%g$QQ^rZn@K%es;QGhksQ1f)ics_#US+An9y6|0JTJ7gR7s zQfxkicMQ~@RZ&ByF3|t^G^{)ren&;+&V}G0$|ay$(A~ps#E4fl5@+>@%+;kTj{Op^ zC`zh4;;B1w=pL>v9aWIVClJvk%_TcuxrD(q`8vI#kbs366yIpwwxsdFHMZk%EenS= zPuT^2LH^VZ_He73(A3t@R7NJ`m!hIRjH#kgm7fw-=w#ZZ;z?%NlHT|5fZmN=&8Bgp zuVLdMWb_#TW=hW``V8oqEqnT0=zpBFcmRlfXrGFG#INh@ScdcwP4juZ2q6_M+jHkC z9te?)Qr8Vu)36!t?iP&Vr6g&Tf@bCVTv;^Zo!PRC$b^Aq#i&VjvwmkQdfDBDa`Gi@ zD5`k=Fx(MUWbzYi0FUU%jPMpn4d>4E@vw}1ejrd8rKzc{s?Es3A^!yQt+C;%o)~lU zU|+pyKT75Fm(EmvmLcP3)cLrsRF`ED$+i&FQS}7ZSJ2~K|8%!=%Ted$pe0@{(sWM@ zN4ze<pbE01*67SF3=x*^sxx34m#c-5;@*y~3B^C@oT6lWSr1qiJ;fVtif<SGVPiej z2u))EPdcXjkgT*Ag8xOqS-;mVb?_EK-xM-EX1J)6`aa_=^w5^DV^Z@@{O<YOkN=pB z@BazxS6>Oc7RvGCBmI)!mQ*O~Y4iD!1*PP-xDI;N#Vm=t0Q1B)1#6erCiErPE622} zPoJ7Pe5S~(fQeeiDx^>ca10-8)p)|G4bsn#XqyfFqUi7&7{LdK4n*WqoGUW*OB!J` z6}VEcb2YFgL&hD!)=*cFJmpx7ukE(upaZ_S)Y&H4ANXdS4o>&oVJU#J_n=>`@cPUy z29vgp5Zk2WLwsI1dk(=dd6osC*m33@f@YKQDm>_3>4B~RoEa1#o@N#pHCO}4I*NWc zC?0c_LIE5tP<!0M5*XZANGw@<#GheQZ5oO<=L@{<_AZYXy`7L+OIY-!^~g639ZY8s z_~H)P&A5X}T+{k=CG6li!ZCm^-hwm`@MPZnwxX&=zN5T7!>|I)sxr6yj=tjMuf(ed z%!;i`9oj<iG>+j>(UB_Vl}%`JxPNu+SH$`zCHG_hYTA4Uj|O2dSeT{+t;wZ^=8hg} zwO621dtSp?`_s2<UsLI7YY^vI2tv01jT78ks4vN>inmwW-OOFp>rRAhKK=Y1n)3$M z(TF#kXRlto2rQrvfRNY^M5hxa_cfvLAeu_-Vb~zyz4w-Yt6o%*AX2ghv9t{&dgJf8 z4A<N&0k3X=t67&u7X^}xr;t&jGLT9cy?wW@yV<httt`{F0{5f`s!Y86p@JH{naEy~ z2+6EcS*6#s;x&$5Y(%5V_h9TRIaYp5R!ovo5_Qm~N&(pVW%PF4vpddjuJmisa+(() zTVWX&G?TAa8C1NoM}a8U$IJYbjbC6#C$T<K1C@PX$-*X8&I%aOQ$J&K?%9f#`$C*V z^S3MC!_bRTQkn=iL$R<|B*A#c!ft}#Y?Es>*tZ;PTK2XOnS?Rn&)EN*<HGZJ$58qr zr`9cobBNq+N**@*$(!l`<{?a^AkWr8)*#MQQ!G$_`1=usLp&nUo9ZTu`-Oy}r!Nj> z{I7xHnID=D?o4J8>J?-^(INV`D7od*)F={YEOY^VVDm~$VYpdik2%&hGMFF-e1Gl# z`u%zcIUg!+Hv6A{X1sOtZ9sVS7nP(6JQn8tM0jjwov}yLizs|W#R=y^J{_g__dRsx z<wTj^!}&^}gS>aTe(xaIzdC5JnfEOds1E``h%Dw>U)v}#5*V^ZN)BtTf3NA7yfRS9 z3cPX<yxx;h4%rsoMh(c7c*f&odJfJ&83vX*Hnwo_i%(nnJ=#)X?sG6Cp1U>9@EX(C z*LSWs1R-jis90iO2GhXl=_%AxMnYMW(?%DQo!Ftgsul?q7e1w~`hNBS*6}a%7pN?c z(gvu_($C3(LFxh;Y(kx=QxZ7vmB#JENbA^DwhInqS}#lhb6LINOL(rW(;tW@Ba0q( zgZ+=Iz7l)-@KzuvYXt4e)7V74^oz}NqOfAQKp>WT5ty|CV;RG^;2X(=msustvsD!X zkE02C3&~k3T>zR~JV9M1V3I5CTi=+p2hsWGUBY+g!TwSwrwY1A!M=|(V-pVjcn%{Y zMe*=CYs0&^@r&%s9y86?*WSpoCd1DUYyM#XX{yx*=>*r7$BF|yXyyP9nvbGoGGRB5 z7v*GX*rXE&%rjTv*ADK*p6Fb#R#O`<??-YzIrCRAi0ZGP^To3SW!+1Q0=zbQDn#Eg zGPh|z(iRNp*KoZ`BIj0pNnv|omYbm2xo--=B4LF+Bz}Q>r&-m-)GQ%=3I6x3Zp@@5 zW$&pekDa^GIko0_DT*sI4gF?u*A_<4CiQMzL3S{}v+XnEUgp#5<!=vAhsCQ#K}$qP zE31P;6NE!=WNrm){1O}6)6yRGGXh-C7+d>PeXE^e7|aeBe3q5G)%8l~<Z3=AZ~pjl z!|x}Jgg?(={kFy~DZLl`&;l(xymR8c_D6bMeEl?T`1lj)*=tBym%`8DJ$-n&UqU6k zm})L<-Q<YhTv5}acIb4<{@bU;e!rC%?j0otE>8~Zns_|1X}WUKu9W8c-F`jm<-V{d zAP#W%(^J;G-y8EkT9$kCua?0N|9<n5ACy?7-)~O$_nUvE`1{T4_vfmv!KsbtVg_7x ztsR0WG2_2~+UoZcCou|0*MB|se?PU$-yi$;rv1;B{r%qm^RauLZbq*g<>ZTz(Bz89 zplooxe;>}MTYV$9_**k6jR<37L5Lkt-c&fRsqdgIoSPS8;t`O9vl8CA#gmEGYXV&& zkXq<XYhMvky^#V{>9U~+Z_iM((B=1?Q|zouYC=&tLCwX8r~^tyvC&D{?Ra<G_Sb<Y zed>DJdTh2q#wn3D;V|2L2nwK{RaI4vqXgVfn=@yD_|5}PIs{Ao86GOdmMMg;KIQE* z)=r3h{WDzWu4gu}%UU3G4gvHYO(fI`WHS2k+?fkDP<58tB8Ar{;1w&PmSa!uk$phR zJ@{Xz?ben+kPCatd2q!tjI31Nl|j)3#E}4jx8~-v=YZ(qIUu^I0-)7^49+Q_B04|q ziSBQ4B%!~HJpL1_2LBDLrgfitJTjddD3w12zqQ|N9Pj&rul?b2<jL~2PTmPwK+TpM zH729P@&3t9v;k)(5Z3<<I5oF>m>)=ZoL+qu-uHF;{5R?JF-So1VHYoUsn(mGKHThg zYDy)#Zh)bZn{iq30}+oOcSwLQncFHeE`P9;mX^EDD&84(;?b1veeD-TpBqq1Bf4{c zzTEzBx)yvqKTQAHp2+7O{QADl9tqJ>O3A^;(fOxKMHgtjs|;~mk10U+d6e2|xF%GG zwti^qoNgoKgoeG-rHVn&mI2P1t~Ji=LpIsoIhQRWlQ$=(BOt>T%r+PDJlc9OE!GGR z3+rwzTeTe~E4WgG$MOVGzE(K{jOb~cTgfImTCBHu8p<3xCqU-n^Z8T>vYM_j#1wWu zHb(qe2xsxf_|SU$7-u*orS7>0M<s&3*U%nT|Dy2if92MEj~r|Pf!D^YjAw9Kk&YDF zYRg7EfYSYU0kL|{2fkGSN?riGY|Fs?c5twbO>ppy;NZ73!NE_To*qWkZ`;_s)RmHK z&k0nuiN?s}>uiJg7uu%1TF)kD*hkSOrlEWYgW3Fw329Y|9k580K%rcBB6^azu}7#{ zEY}R?MULI>gTse(9em|^*VmT!SUdDvEcM%sGAjbknNS`ym8xY7PP>^Ws_I@<q6+|| zsa{Usp@{(`UrsZEKydyJ4b?0}TNTpl$-1K9m$Q7^X+4*q#A40|<9da`Ac4P-oLv#! zBVnEMkctRZci+UzhOz>dj-^RwtyDu+HozNTtLN}rCX;jS7Tz^YW!FOAZwYF~m<8f| zG!4)_VM}UK@D&+9)1ZnZxx+MX@A7RcZ=lvw=S~9JXFJRUZmn@(e6)~Xdtuvhq!y?g z9+=*g2T}>o!e1+M^W34i5&X}8fuE(@KY|R8edv?)N!BF_F~+&f>W(yz4~w<HCkOac zb$8OcQ!54%B3gRgeK4Jvew~!qx|z1miRE(<0lb!Blc8ag@Ow1h>lGK|?n7TKlLg*8 zB^>R747d?JL5`FB{;5XsZHEZk0BV+g>FaiWMe|+SoAwR8-BN$px{4lE+r@T0YC|Fl z6=Qav?MlZzWvNj_1EM)VY3&_24aJ||?f{+2A^gC<)FDjR_GClJT3z`{o8U%#ofj8h zC}x6Q9{i*ywP9eusiCiU+*!wstF=i(yCZ>67uEw1p8wU4EXtuAO&+Ap4k~D>U+3U- z4BoVwC&ysEVJCwgz9Mik{cVmv*Rq1JbtfQnX$9U){zbuOF2NxS?}2#HpY$V#j|%-2 z#g|s=Hn_MXhZODrHl||5LKDa(O8-$sg&7FToK<;1wdCNW>(w(rvQV~08$-}m?df=5 zxMLGv7+6+-t?TnnUV+s@l7$8Y47JnIK!UfwBq}m6O|DyAHLzm1AC9h{osv^oI%k^C zfOV2a-A!}0-G)C$+>6w;2hV_aMEw3FEf{r<6(j=?#=^Zcc+-dk2c%`RJ8HKt!Dv5& z*hDUH9_?siKIbvl`R;Tp|Ciw8pBI3!r~Fj2RbM)6<zY_hoX~!07Pq5~^-VU+vwsm9 znieTn!KZwi0_i>lauE6?ePEk_X0F?fgWRsl;ys_D#JIw3wTF<VIdffYkSfp2uEcz7 zNW>5$TUIDzy71F6G-OfLfIsh|!3QBrmJdHzJ!a9*1fi^}5o-OnisL^Zdh)AflR#^w zW1MTlRzHQ0vL7Qf&Yj_){mLnxWVYl?++&A-oI{9TI>QT3T@egRH3Z+hmK>)4BW7hu z5!Iszko@J~7TWSUqfKLb^>pPS)2(i$QZQS2A0Vi~R~S>+)7)4cr3y`&29n`E0xSjY zK49Zk_ZjA@yJ;ua36H9GhwG0Ip+|qz#{G6UQjn-Eyi*1_x~$Y}_22D)fBbeJ4Bw!; zbrQIt3nah0W<;kHorkPeWw9>HvZ2fKqb4trk>34xH*uEefr~$VG6FB$$a){Q-Q7f^ zhZ1CA)60YS0J28xv+f{`TRvG?0&&6%y;u<kHGb*I-g@};>>5|zYS}4A5031iEXeJt zz@iXBYb+r%v_ONgn(?-8#m!)af3<g1c328IyW0Z8=@F|#S_gEgYz%bu1-|qnmd~6K zpdxQeAy}x+aKhU;Wg{DG$~xw*9ZJzkmK$!>>om$;nuya%o#G1T)w!44CY#1a5G_d- z%~)i*T(vna(fM-YeWm~9p#R*rS!st(6DP;GlQZM3E^LD>#`DwGmQ6@p2h5fcz+TbC zZt-W;l&)C4Y1%iq<aHIW=or83vQT$gGt*RlhqB%>JzakL-cB(9td?}RQug6+cVO=l zoWIQ18eYDbSNnBQusFlRwk}j=&Um<$=tG{6zx<1$Jv-rG7#DD!b6ODrr9LA;H3DNr ze4##b^UOC6#1HB-cj1z#ai=P!@zUCFX8T(4^}EN;mnT=xIE-q79v(;`ll+t)M^~m+ zl`n>^fSxMaN|yDHV%P8eqPW?m{=CR+b}P(3DJaQY(0c-9&GCM{^_CXIb0>pm26TH$ zvW9L_P>^KAB%6zQC@g&~D@2seWJj(^S%1L5bbF?M?opn`!fiy}xihq9*3uClE}gl- zA_@OjV;=lw;PZ%Ym(H+Q0hs8n|71zCvPBcB>|0nC;Mq~;d~X)Inr{RqTmuo5*~QdS za&4T{aOg!9onRQthBi;)VnW>iq3ylHn%LTYVTv0Yh=_<%x1f|DASEDO1%iTrl!OjS z5eNjNcjC6ti_}P$&;v>65UPq4sZx>vp-Atb5_<8B``Pci-RHc|^PTUzuI~?+3^S8q z)|y%On*070%)^=j%@RW1<=n|gEv7^N)GZVXEztLTI}}&uoi+T|OUDMDE6$|IG|Kf( zq6TbRP{cx!DNax69wwCe?{<{{nUT`Ratuyw9@)S2eetll$ioq%nIxz5HnEI5nQ}j1 zpJVSow(1k+jDIxc-)*V$Ee>*_fJ=KWLzig@HL8v3-#Qy=>F1iVaLf0@cvGk<>Ma8u z2Xf#CO;GjMA2h)L?vmDZPS$JHw#3t20&mlAI!};{h@6NB?cPfqm^AaozneLHRY%1b zI>j4<8w}pUN4uB0w40LZBK)+JligyXJ*CNicYt;+MdYa6aP25m&mx1fSnQ%+pL|;{ zte7Z2rN~_T-*>D0-Z|>|J6dOjm$G9n=jgyBWdMDbEj~dtzE>QH6pu_TbBUI^wrDr` zy68h3-^|NQZ$DjyPX{roe+_}ie>((<kzQg{lf@Y)+!!+aFvgmrZS^^NRpBLbkLRdP zc%A&jUmk<_+Tcm-`!S1m+6qN*2P;UWfo_<izN|+Lid!!T<9zb(4wGA4g=qy&xVct5 z9MpQ{swg78YE#;DMeS3x&P1hcI>wIc3F`f!x_yA=^a3>Lo_hZDP}#M5|GpxsZW7Ar zEFA|t$RPaxXyOp3ah@cYWQH6>oT;EWv~ocF*FO2jimb@IfmRS=w3$BkJNw`N*G=U} zt3%aK&2JkF09uLEp4M((8mi$G9R~+b;g{#9gdf`}NlUa`bnI+{3^|!*=Bh1*%AvlV zqPs-jUW7-TK2669vu6JFq_R5IJr0D%a&xSqm#ejV)!@4kHrAntLa(M@9^-KRiOTu8 zD^onklCP-qaP}=R1o*ywfneo@Wd3rC+{Pm&<e$MszT#wWPML}5reAAQkE*s9Tf^7E zvX~FMcC3QF9XY2BVg$bT7KBi{)&#m1_g}T(6>GXT(-j}POsZ!8*=R^d`zDGnvKfJV zR`WfrZCes1FL#+-wut>X-BaICSDiiVbachm8+~y*zB}H^kz_iXC5)ZUpoftPG))my z?2tjH3BaH7bjr?e`g<TnObk!{PqCzNSC_u|zi-h!<{adS^ZFk;{T*yVga(tMq6<k? zO83J1e>(#fu3k_~uC)sFisqR1^5WbxS8qS9xcWWQCt-&xr1{%_2rfWuDLrZWKew^@ zU(vf^nn`}sur#TPJK6`5<HTyx+0wO0w#?8L{$-T8{uC8>nvRQUnECGz>Vcv?1;p>p z?WES;V$kbI?bae|trk*{&bQA)Rm4k;a$bZ15q*D6;e}}P6dmt2+=GHxLxqKv|0Aw2 zlIxT}C3d$oC43P|hfKVGpZ8(F-%+IQm#M=qACcsG(;qa`75}^|tBnp~FqDIH=z6)B zl3G0t#pO2lzrwf2_y0M3hXnqc@ZI>I!gqiX5WatzWBj}D-ST%&GioMp703z=eE=*o zO{RE$9}-_SABD%~>F7FKzoB7QJz#kp()6$PMvlB#&>JpE-fNhie3G4yIy2$xINbg? zP%=EB!B+2OQ9G-jy+Mn=-FAW<HR$dXIn!+3ub1a$jcfLQ=mP2HUk1?efGZ;0Tv1NL zQ~(I^0FB;3XK(%;Ls1YWVD!#Qx5=?!0{`7D1}-w4KWN;D3P^yRV-H#GdUesGx2z%w zv!zq3OTYa0W1`ylKi<=)vLSggV+-+Q#;5CLIp#XJ9HdBC?UZ0fra|xb;F;It<7+hq z=7^Y_N{TdA9i?-E?gFO_%?fM;%Pw23l?1qguj;_+d^!05JOlJ>6)NBa9S>8U^54&# zElTzrFxyO{{kzrAp&p;312a`D2c2hRa9hd&-qn0j{o%Ci^Z~S~sE3@i9GqPLG8eV` zX)X%a=u+Tn>v&_tC)?2Rg%1TG&|RgCLInrXn<ivLGM;4li4$YALlukAf{<+44c|6O z8MUrc)J=R}4qj}E7M*@R$DQ_2^5Wv8T!?e4LO+XqJ4ykB1Ii}VFKCFa!ItYa{ir{b z<}?a4_^;-Bc&u6mH(yrTtFPET<Ert6I+=-hL@Q7nu<2vJdT}E5{Zg}H@ZtE#!EdrT zU;SPCfLzuKXybqF%?j*p`H!v7z^Hlz-Id>+PSO9p+rQiE>VMwr4}oHo@_D+;zabve zF&UuFBj_%xjDGle4KAxk&(r#Mm1GxQOs3VVJj$mAGi&e7lW6E+LI3r?0VietWydsQ zn){duR2(BcJ2K7q_6x(KUv^Xpc3Ja%D%^NwdvGw=ud;<^sI#Ygrq9o~8bKH?mg%No zSmU*|OI*s#ovd9W-@g5W{eobqQ?gi~!l2;O%@%>z!)MH@NFt5x&=rKPZ2Gd2g`fNV zr-ZJF=swF{fn$;kj)g@MXZR@vFwmr@aSS^_d;U*j(bzw5IN~KHmO|Ic$nZ51rmhw> zv^(XIu&>A5^bb7S1Muuhx&Fv4X^LTii*4;^U5_Gwq_U~22fcw0NN<F?ui6E&uo9T! z!3Td!JEYMil*WLNKc~7&E5XIXjUNVm?p+ZTZPywXo^?tn$kP%4P_4CpvgZrx6>)!~ z>E1~!oIrJstABtDj;%{6o5rU2gM5@$P;yu8ZHdrt=8%jBqyHPG?j|E0&AUI7*vgjw z0B+s?ACX&GYP<d`nu9A?mO@W$3-iQMI49HsV=@C5nYR5}cRfJQAB0vB&U(lo)-O+L zCvZUe@s%iL30n3OybhZIsLy@0d=Mo$Y>>8%SsYCm|F!=&$+=-VT3)&I5J17@(Fv&; z)Eid1o1(Gh^gSc83a(X`Ub)PVf?wefLW_B6<Doin1G#QW{T&NRyO12qc0SAuMmPes z1ssA5zBGAZIveIU7wNlFA@zlx$wE|HM5JihIyu#N;K_sboHz3zJFXa=h=>f2@fQqm z9=TikgX}d+E4c5bq;k1S?0b2lP;*gtF}6z%)`3CCimjKnePj`B6Y2S6OT!6eHAs>s z{(NNcti8Unay)5Y)pBVV>=HSYd_c_1!z?0KgbJL?aLN}z1s(|m(^^Bzh_HezN;Gwp zdmMvBQ!J41A+}44Bcg_e@t`IFIVcApCuwfSCYjl1dNGKj`q~~3|BCbW&TinnP5tqi z0;|XC48EP?lN1H-DSd>xcUetKaq-Mr5%!!%y`M?P!W`@6-5dTp$3)#H2J!}Zp~zcR zNl8gWU;JaP@>uB5vzwPXO*oe3PW+|F3J9`V_jvw0K~@=+iK`n{ew82Tly;ZC|D^C< zW}xHmSZ{k-FwRs)H1jeLdF+x?L8O>T)n^Bm$(1A=7boEkLUFVr)|PE=pVr_OkVEy3 z+Bsk@T{MZ~BuX338TcvuQDK2jPdv!C8Y1zt;Tt+K+N_l??LDpE48rh@V=<%)i%K2& z;ibw;7nJ~22!@&SMt8>fpN56~pm{l@wzv*M+vH(p(saZLWE~!AaA~rNx{HjG1>1=W zGuxlI0WCAZo<OVFNW;u*{SBm=W*#WIE9nzN&(*t8Ai-pDGu@dq66liCLM$+air$>w zY14OFs8(eB2;tZ(o-tUJNB^Mt`pV^S;2&#)@yA8GHB{&in(z9@QWi%%L(1r*!eaqD z{&Rsuw}NeMJ>>4)nqbFYR~&#tkoA8ZBZ<rzjUBS8;{Did`xu|v1Xgar09=k!u#Fz^ zf2{EmRW2PzG#_hPwP{cT9_~kO=j^5=9^TyOG2%czCIW!cmm)s%sDG@*E@wqHs{=2W z*TrW9_mv8d)DQFyAG85ig#7!GT!Bj}`Q-xpj|{%KE&@s|vw4TMNR2Ay!e+Qic*?z& zEU>xbQK+HS@p<fIs@1P6DwtrgBR1q=Vslj1;&#p!`<li#sVkQ~=8L0MN|ylzxDRag zn_BZ((T>U?z2_013FF8M_hsq+=y9#m!*&*Y)ZNl;ZARmN(8Qy@#s1e9NREG)QgwV= zRA;_CotKjnzB!~T(q9rGgD(E5v>O(N!PxYk!EV(p=(!kO*h)+oq-@Q7$fV?tD8k$8 z25+}RM*5417wn5sXQ%u0xAv%tH#b6dj12ldu45}rd87j+ul4rJ>)&@XFD%3&5N4$d zfNy!ZVwFFjPtAl<0@Q48L(qJDd_H_UgR_)RTQ01dsL_`RcyMR@I7Ci+W4s!r9dBW& zlK>gGULdGn+&x=XV@ICv9cG{>My~*f(--q7y*4^-K_$yJL=Zz4;!@IRm&E{jPAh+H zH75e&4UX3wg8E3sHL?VW(|=q+(JIq%BZJQU-=y0?lY}J1SoiOj854y5l|%=$<C#0d zr}Lg`reuUo!yCI+)jsX}jr*BbR@>&=lr9(Kupb&OH!thfIc=HE<&Qw?k*R6B+g9u^ zZB6@q*mCasAa@j*J|L^jqMeBq;ssK~u2tO=2jQPr;&`QrMW8`1f$x;AV3I#aM1)=} zfB<<IuBHa0hSgN*PyAd4^XA?JP&9xRy4ug>&6~D}v!13D{r-;CPmqQzPWCT=tg!Ml zPgq+WsL^4g41#L`=~?@<Ph<^*?{p^yZ2Y#Yt2ginnZF4Nj@<P;;Mn7Wl(|v^3S#0X zza0Q2m=z|I^Bw1b3J`$qIJHTME)lLg7hb>I83Q%t@R7eH=YL<S@$$%yC02dgmMT#B zEw7mSxAIoB<J<@0y@O~igPj{xe)Vk-VdY!dSy?tkqO4T<^tk=^bCGUAyrvQ#k)~!E zvlBtc4kCPXv^WW&-d5+MZ2%RSPu1{gT!$LT1p(SCPoKru3$~W0CAcTA#W9AQddEQf zl7;3GqvU0V^J=#N8thd-2OfYP+lJZRyotQ+O>bHM)Vr7BR-9E?`<aijl4JxbZwn8_ zS9QaH?`z88PxX3_!+O;lk4wM&KF!%En2x+lwA%AEA}GtqEjLeJt<}(QH0smW57I63 zJ!$5@{o?L)PWLq7`bNx1FsbzT8qn~oxBm+df13Y`QxB9OVt`#+TLpx@cxEwmeW9}{ z3UPpg6|^Swb8NL%7kJ7X$h?03K=eTjzEJI`OX>39G}H)xWGLY(a>VlzgA{9QoZkuY zZ0?fBa?1H*=dCa`NbC35Brh-UmIoR3U<nSo=2_mczSG!+0v$l1Iv4m~nPtJQp-byn zSQzfJoWBj!S6EmW>6X>*09I-5|MdsX#&UV$=!`3NYfH*IiRw5HjVLDCS_^PBk-??_ zCMJ_g%w*kCDD={`XxGvfxLRl6{SAy<KwB|K$GGEn3laAzj>O|Q*DK1!_Ft7PW<;+$ z?Dv?SH-6bDbdcAi%q;RnjZP}YILwyz{HY6Sk|zL=+D{M=?K9LL`}C{{_(=woIN^jX z9Wx6miEu)Mr&n9`<BpnjV@Ppvais;f5%fmqXfof8SR;_2cx5N*JU1N&Z3;?+o30%B zAB-seqt;He3c>HLOM(lemXbmHp>577ypnC&3EW%Ve$nGYe7Qy$&YZ7UZ#*rD#ea&~ zqAcBgv2kLoeKj9L$#AUp-x`X+<(4nkF(YMyJViC+y66_f44rsZMGQopk}$JQr3{~b z&OuLx<xXImaTEUI&kG2K3MOW&_lpE`O-(Myd@d;o=ZT+*Gs=Vrig}QgX?VsST|NO& zvw!}UNG5IMWqE_wXBR2I#gxli)YP|F7uJ2^zjS7q7@6sdhI^^MdfM*cOSp-oO@KU( zxqj<ye*<`=1m9pH&VkZj#<bQf^?+?dtMPS)fHsCi6C=Dy+M~1>RmFmXs@>e^V{CK~ z*+M6TyRdIH4Gp^{A<(%)P$&!eb1Q!nbGlNLz1<+FFuhkz-5kV+p{;GXH9Fr|xGl#; z7SPjkkI1ZeP4llQHofM*SU~@t%JC1yqrSF(FgcLFkcfCxP_TJgLHgA`6zVfC9sdcA z&6YgWIafM<`i{_c6!-6RSCPEVfY?hJ_T5b$-lsnGi0b?Un{pSKw@)&k<e2c+m^-DG zWXC^o-@Lnb%qZRx_i5C<>Qg!h1G;*H%A!qGdXF`?5AN9gtETLn$od|7!b^Hk(&35q zeXckH7rDwnXx4DCu!t;B{Vn6*+E0ahqbj74L}b7b3BTO6j<gde1~2Sz(%j}>kb3=z zk)D<X&jS>i>7O?5$o!x=guLlYYj-00U<--c*wa=9>O~;us;Ej`ZOK*<5e2~;ig+EY zWiX%uZhLHIb*sJ3YFBe8P<CBith8=j$*Vu{I0(SroiDEzPL_jdY=07g$+kt!n%gx3 zhLo9C21G28V+Qr;!A-BL-Sv6tn*qs8F<A)t!D!=P=VYUbk2^I-vN$jC_}IHG=p_-* zG`bNAn{Zq*U1+2-63itrq%99K$#+{1#8}b+aq6OZ&t5>y%SE71AJLTGPoy9)6jesC zq28KL7N1O`U8Rq5#f>`K<=Ah;Vd4BJu3B~NSI8Q(Td7kA&#<TW6GzO)O>6?I$i?O) zYEhvbleYSU<?u5FvW^@*%u;%iVX=Mh4H(w(?B<{io_?}fl@W85R-G5ADo^XxbzJ<3 zi20HaI-~hY0-lb7c^&g&!DrBDOh!`T@^~jE=ZYpzFhVnNAkjmtJkr>SGyi*8S!Ir} zA<|t>8|S^rUN>2{*q?AKq(QfoM>moo{If!7YUc|*^9!x_3x(fAtsyG>K7LmlyLSlY z<ai(BoMVJyYGb|jbn0CV^V%>%POR>;q5t!}q7v$kNK%m5Bn7e`mL_O_Hg$G2UfLbZ z_67=@GXg!vvVYK7h69zu3|K_H8OX6p0qMn9*Vf`tY+Y^}URr&v09JpNbJo>m8$DBk zRd0hDWIa*LgNf#ib_EZ+tW)6l)E-_4MG;XDAvB`!J6uwrH$f@>4j-6<?9aSR^zr|m z-_*$2x$WiP$i}q{sZ1X;jmpb{nc28{kKE{Gv)6oBoot)C8J+gko|&P9u&Tn2q-3{1 zuh08uNQsKtQeSryEJ-}Q_pVu;K&ExfHyUttM)$o}(>9w*gRW$RHc)P%$_KiP^07ww zOFgP5<mQ7EzvU2PbXMg!=BzPO*TMnd;kqDQT0m`mXg$YH6jN(Om*WflL1>FZK`Ogc z&1PuX$~Dbdbz_?dnUCa|!FedM4^7xr*6tw;)>pOkt|3Up1#1Wt<>kcz44(+v=W()T zH&vr-kz9rJ9Fj?LT^tpuON!6RxeIn1Z*u4>@XYGDashiL*xO*zK{%)=OVwZ!!I9G$ zTJ0bI-ii%TzEbd|B&R6c{Zn@QIBZ^teF$r4u>U)fXV<2Gf6(RDUO%$l9#@cT77|~A zulk6G!uHDbp!ZpG`Ye0a5-MGgaO(QH?&{SJx9A2@_-KEDm;*+y<(v6H%WjTY(R+iW zTk3N{_{L|CuC%A-(pxCL%`)I#*&XfqK@)b{+2nsij2r@lCU7-iO2NFvFVKGQQE6pE zCJI%4jqVn0&cDbD)&I}r1?_*47mEKcbUpqgF9c+Md2}V-?kCy;sP^(dpQj{Xs}L1h z+T8CS#R>C5IkV0_nX^=n!@>yN8a6@^5k=`BpVZKbV*}Bwp;25t+^0ACzyR;OH88qT zgsaF|i^YU5w<B}C-l$pIVVWTj*)wkf4&KQ!epLw7FSqzTPko6|anh@cywAeyrK_iF zq|0V;?OV@@Zs#~HPQX`)O)}<BXiq2TGTFGZd|AG!k)s42)K;+H%h}1~qjk@aywD?_ zY&8*A|M_u`SI?KMse`@<HI2}mGBve30D${ke?WZ+QoO-rU7(lTbq|=a8Dq(3E|oJr zNO;geJnZYsP4S<S_}r20s1P=ks3NJ@H=fwE9U%zTZdKmkZQi-^F_zPFBIs>7T)KZJ zV`xB{o#?Nzj?i9C1?BqBFd3&)-R0chJg^I7HgK;qa~@(HcV*{*bAVts5{8Dlhtc8T z6AMbn&ak0vO!C~_T#24b6@xBK8y5`Go1vdq<7O*k))6{iU|KGp(@=R;Z01Z0$(cmo z0!&==(Ll8<1|8t`ViDfxmrcDc4NjdgF{W-QsS>UxuegQ8#VZs*N^o${7T~OKH#in1 zwW%YX*21iE^-61dngXv+Z4_&^PtHSlF9_)sGi+diAl5;Md4=MaMubh<i|10hH597h zcm<;rw=cF``Kt&-?8BP-mNkM}53P6#qpP8o!qXV2VJDy3Le4%qU!Hw=n4Aq}C?7Md z?iwlCIOr56y&Oyqn3?rQD|=1qEv?Wm<}^(rNaV3)6Lq!Gy8OFUVCLQPZ83%mNdH|h z0dU(0k}9EwR6fTQYb>X!Z=X3+@R_KEz{cdgHUzU+u#R(Jz>6{PVk3+lqs#s8o8KFJ zx5Ye4W(eE&!m=-fg@r&gbft<ge1-NO!q@x*9SBlVJR_G!E)$&MxUw$1G484shB=8c zMi8I{u__$k^VA<S0!gDqZg;3ixcFSsL4VITMy1QT6EVZ@q)0L5fZX~|$YUS4wranZ zn2q<bXsFV&0CTf(E+dB6BIa97L|D^$h&_g|8(|S`%@{D?bj=lpweXl4>@;X;I_zRd zuOGL13w|;7x~Z$s&iO&S^OVVN`qOgkN-Vh5S?KyB1~P|WEB6)|%Qjp0A$C53eDR{h z0ZWMnL1nFDGk_U2EJVjAe`CibIpOX|aL|KhPe4Q^KqKjDT<-f#%bzTfXN*&+4vten z>S`qsLaF$GpC+M04=@<Knwl=_3`$BEsF55B$ps`J-F6tfFe^ff!nQbEaY$zBrOi*$ z<@}f1_K98ntfPSxp4xu@OrL?JMvEtPeH<RjAkbodfB~tn9W*n_mu7NtZN=~<<^xl! zm#?qo&hP=fvQj$?klQ87tP~*(o$iv9i$Eg$(T|*(&NOlrbw*BQ{-B|;*lLO%M1CDK zwDPjc9A~r9qi$eu5!X_R$L2-S(?1dA-$t1&Mih;V89Lrg&-8z+wB0+Q;JU8-p34wS zmDRjgyp~i79q65KD0r?GFI#}c!7-pfjh;NxsAS#p3fm=Yg`xQy3|X($inTyphYI5- zRP3DOx09&GD%)MI=_1?LG{4)YoApG&KtM%5^g7B9L??v2HN6+;Rs+a6pZsqB=+Tu^ z)z1KEQtaajGTE`Qe~{f1$!|kYy%@8l!ME-DgQjkWxBtbS>xlsaU2Zk8xp={x(WX50 zPTrw2njyr27Fgd4X+)gzb%5_vGUk}jZO6K(%V*@`y^|c9Yj;}w_<q50qI(p*Oeg^W z0s{D68hhjiQe-h_F8+dpdbWiIH&?l7nt7|2bdol_#H}-I9?zd1Mh3~Tv2+4zrf}4` zCWG<#@w#mXM4!!ei}`zzB9M@{0l$N7kqyDTV>zQU+D8)Pt{LSWbkPX^kf~j|J&e*h zzGFIfK_yke^I?Dcs+zUjryn$k4OyjF+bHKa0+Nf&Cjw}O8g6C3t&cXXVL}!HG6C`9 z@TBJHZJgTiOV#_hzKb@@>^nSpvzQUXp<*Ls8e{OF6+x;uj-`u#;$GPE6B<ki1b1~j z{NR<jbY<nLi9o6&OI+K~hDUlM3oEJM(_Vc)cl}bZvvWqJ;eGc2rO%NmiIH95M7cT$ zvZ1Gi+;3^SGW4dkDXd0*XsJQxe!$4isime(hZy4eD#}!b5^%9_vt4KCMY&<;{ZV^V zlAfMN`lIRK+`+Qz^tsJuZ&0w!&mx#NncfqI-qtHrwK+rc8YQ-i?l+eQEo_^Dm6*{Q zt>sQZ>_zbTw}Yv5U0#@cb(3r~KEjV*Rm~hkC~?OKK+hl6I%TjqM&JVS@#O;=cR+Ux zmsX~&?|{%4i9H|moElWkvhnn$d+$v>D&`!IM~3_EoQ+F;mU9eh{01E-QrX?9hp{Ga z(6MCI$w$Lo^km)5y>=P5u8~a-d*#b4QJJta8UA{jI$F&6D@dR4oqJ1U%aU!vQjv=h zT>gGB*ZiP{;%fAuToH<&1<KYLPIMbPEB3Ywt_iMPq7az|;;wmWDRHX`#AY;~Zs>+S zD2O<Z{+8|xU3lC#zn2^cmul{sj@e^5AJGA@j+U!&->aG025Y2|A|_TB^#Qq$$dTsb zm7z)2y*|bPgYHk$-n^V~YgYQ`Ip)Qx8*7;Ski0Ag2MS-HW{SK8gXPPqb)?DxkKo*s z5lADpb9pG|yCZ%}f{|W!;dOEkJvXY3?!<l6YuXd+sB<T2&oeWhZk#MNL3Ta{G^R6G z_mbbAkh;(P^rzz3cyp)8ZU{_S8P&gS!Fz&Adh&o|sKj$Tpkvo5z+7$p)FsaC?V|U- z7fEg@Ka&Uc-PQip%onJK9u%{{6LmsT-u)fwHtmT^s7%@ur~f<|isQ!#=7g{D!Y6;) zC_lUX9QB}=UI9tp?b19@V|;`kLslqK7v78`^IXaftZD(kwePiGU#$Q4SGp6t-Ad0V zEnF+cG{329<@)-QD2ZK}&B^!Y2TPpN)Jih3EajVm0fZ!jL#U)q$YA<yH_<kM@M$S` z8AyerrwiG%I6F6jYt48>jQxH=%#+7@ESR<ec#I+JJ6hsX_Z%`O5v6pKP8{icFg(Ih z5}?=P4g!Jf*pzPb_Vq;f*SEAcq8@%bjoFPG!!`~Q$(TA(wgRdhd2H8VacQ!11VhjZ zoLXK*FP!V*YUm(?qxMMWuJ2~$PKh9;tCRvqQQqGA$7|AEs~3g#BQU&j_r}dj`9SiJ zyPctcw-^VxKE-s-=j|vbQk-3p<sqEub-$v#f{(Noj%dWh?S~%n9AeH7@$tA0HoLBF zIA=I-^q4iJw<SFyy;%!UYc(C~ybB)_nRrctt?(SJ3*;u4koM|YMES1=Hmuidc9JVV zWNp+ITxl`Ab7<kA1nk4IlXq?t;<#XHrjw}%tXzg63<MG##`};%lI^<g`R-3JXXO2^ zy5}y%z`UIVb&B}hx2UN;b~t@M*cOwdtIU@&2kJ}zPqv8uhRv3BtW;xroS!_UQ5nF0 zO<}2ZydwVI#X-C8(jF}N_nyL1bc^2B4;We2Trz89%}2+8p{P=0j`$%D`xLB^97{)r z6cw_-^tdKq0f|<8o7}kD&uKjVILGTrTy8%(m1HTrIBx4yJQEKu(BRwbj%z<_8D$fs zY!7DR3V(ewi<#qO7!G9QJKyMLQCnx(lDD-rO?>pc%guOUvsw-Z>5-pX(c%m^;ml<C zpiaHZgWThs5gEZF<omGU+u7n<mE>ejTyl1;MuE@`F!KH#UHwWHxfRj()@oj=xkxFt zfPSpHrStdvv8S??oRJlEK3+nyoVo#V%DH1Jr0i<h-Y<olk%6lP_wu{N{iRZDkmyw< z31U&ABK{=hbUmlpyPg?)uZhGRDI?;*<V?u;!qA|Hd|vrA;n7xpw2qy-PjkNi7y53) z4F;-9%}ttmL~Zi;^kBV@`;g?o!19reZ;9Q-#32do8*HrlQ*w&S8h43K1}Mco$af67 z<}~Vx6djn(N)`1`icS*QY~>?ubVVVrNCxkH!(7OuGA4Z#tE=su_nBH36FIJxmD{eI z-RER>;)av8Ga_~h-LU|YljuA-JT#<m&^Q*fp4s6zaTV+x@>wR5J9Eym3#<b&j>&s* zzQ1uq)9C&+T)?cgou9<3d8MpVsEh$hrjU=vt01R3{})eha9C52E=|KOsUg{L7Zwc# zexr|jr~&sNHqKY(Pn4hJ_$4trR;6hWWRzuV0{k}W9y?U#V+WHp6)fPS!Hx>UJ10a_ zm3v}MI6LR%B<Im$@}P8p(s2-JJJaG>lett-&&^#(&BIZg34UYe)|MYTpXOH(XtZ;Y z<0f{inR`NMdII?K@Q47D>d_o`SItbjd<2`3iCOxMa7G<}2_>8%1nrB-4n^fi(+ShY zpn9a~B#>E4j*f+nkI^}QydE5E8%9Tm1HuOnz=fHo!or?$;M*+UxjrF~D2~nP3S&2P zNe5O+PT7@KZ2HMlziDxhtGE*(^-O?3mr3%c!~nMFUXF2PQSo?3bqZd{{zPg-SB@C? z;#x_=s1vsp!g(zT7u_2urqg#$&0I3O&)tp%hg^<)<6Axwz3uXKQCWERX?}LoauB<Q zQvOn2@$%uQLTjD6|M#{n594^*>a;107fepFWY$}D!a}2MgIyuWs&uH~`kiW-#(Rsx z8>z7m;SM4;d4D8GYHUkLM0t>JxNwCXN7O=aJkJ6I*TEYuzTf*{Z?@*z@Ngd^m${&j z2qb*(T8#m$A2I?nHr+CGam=>f3?6~A%R$v|X8m?(zOF_oBa8wBIPi1<WQ9q@dn{V; zw>IoXNC@SQ8z%hgZ8DCJ$|;D>s`?|MiNS2f<|VeBXs)o9jGCHH)rzSY#miKU&TbY8 zKS>^~6w%NRzqaATfrH$g>#9#JOfkQVJcvB^TaEug1?5WK_Ju#n;Y{q-D9G~3b~9Za zGG#^BCI;hp$*Ag@y}}cyljPhQ(KMnk;+YRMdNb@Gl-AVA!t&N1+5I&^?}j3Mhd3$> zCb+qhofyA2a<VlJ*m3gb#IS2{fIQ<C$K>=rMN`{$9W!v#p$?UJ=5&X3f%VZU)PW2g zA5tu6#y$k$h3uyY3-mdFxIOGgD0vFYl}dIGo~rx4jkslF<01H&XxzcoE$07VL;c9K zcJlw*V^&!Yb@@Sa+1zj)MqWBvBL>*l1jr*3rKQ9YFq>wtNOKR#a^`#e@)E@tu`qGT z$0bzv9grWQdc1LSYA3)qywvs5%6QEsa#iYS6ot=7<PpL^&g=%)sx`lvRNNBlWE2LC zH_ybO-#4c*wF=Cy%QBmu)p2PpaLcYCE?Wrll;l*8dmi-)jYS1Uf{OJ8x8oIZph$~8 za?<4(@0%mhoVXpEa2FA0MRnqc=T@J-0|#7zf+a?0Cg`^4>Ga7)Kk>edd(KeS=j{jW z1_Z0$@}SfZ;+IknLQ&sL#M%8vC-EEA!d^->x`Tu)sWJl*ZfWV_d$TZf(>1tvSXhbC z@gz!YlPq?#Miuh(bUCzd%nMCMau)j(l@pyrKJ2w%4W02B38Z=<%z_5T$02hu&Lo|} z!DTaigNMV!#o}t^SSM+9==1ixW$bI0MwyEI<dOAj4;vdBJhAF212PN*tdH(}tb0YX zij?Zpeq2q#r<qctwVIGW_E%iQJm6`fOrP{BcP<^|C`RfmDCGF{^Lap#oza%&a(dj| zpA~}(re$@$_WSqOzo<y}T}I62XYT2$mv~m^<GtQjU6e#`6Q$F8T+>A4f-a$PY^%#y zr_|L7{95|mjRNiiROzTnn5f>{N~L<;Q8CoX(Pb;jhlvkdsv1jtx0h^_%y(+e6mg*E z*i1JxN#77nrp20$#jcD<Z;P-}Gu%TGy#C01yDN9gbG1*2`&v*u6={Wb$6G?@;e0vY zgU8nxrj2Wt#-nHJmeyM|mJw18HKXgwKJ*RK=u3CwE6SZnu*b&hIBUKcbRolsVMM&i z4cLlw4M*CGMd7P?UKUz=`GqhEj`DlJ&~XL2g~>YkFLx1S@XS%l0`^y7rgzc_FLh)p zT_(N`*3^NTpZFxN=s8rBXKHpqqLVQ(UK#{HbP`Y4_SVLG)UofA=f^ZYJF|?Fnf@`q zNH%?8g6&PEp53LLoO~@6>=*d8=eI+ejg5{g&5Az(K`NP7-ZnquKH75v=Bu#_Ar9hS zP9lSE_w3CNGG2gZ^)Y_7dUx*l@Q<|3u3tBj&i_ty{jWoTBeUo&+*)f)SJdF#tVU(& zw+G%ABB0(y_OZ7g;KLI>ECOD>!EAcGiG77*FWOvDj}mAFZqW=V;iv0ey=z>k^J8Y) zFM>phD#xqOIzy!v9XYRI!wZKz;&@*|+ueCklVmjV5AL-+R@sFVEflC6csqX`#W0f@ z(xfA|=w#K5SNlOjr$G5G;Myl~@>2ayb;E6Em$#pZ_233+=;!wJjQl3NdS^%B%2jm_ zo}Eo`em2au(y+OthL~i?Ug}2M_@%Ea8I`ei-dfs3BTMkdNG*?Ak$4Uug;HxmG8td2 zvg@*eczFz0xTA~jzcBAQm5Ovxr10<EDdV!*z%xs=GK3QGMdjeb0b`eRqBQeL5|D+J zzYg8-)Un1#;N5C>cy5Xw8W5eGqbZd6OoFMb&gZJ~I&kI)f3loGGDiuY$o}RH7q}dY zzepULu1~45=33e3V+3-dzM!XS?hTUg+2cL@)p4Fqyy4?Oiw>d=GLj>OHPT5bM|aHE zr+gS;=uHXYSsJx2$j$kqwB_stxl)ouVKh8|<t#_ylnA`m?0eU`9dzNe`jl(v4(ElN zjC-Zi=YbmCv!|l@q-APd9YwRMaKf<2*laW>$b7d~d8t>@z$shek^Ftz@;slqa0m2e z-$tygz0(gGrz^(^$UawwSv>^9D4eOQmLgbd;J9xkAD?_OhZp1lu2r`TYeI!@d%EPl zFmXJ~gU`<oQk3mNfIDy58X7@H(9U5*!$nNkYS4#`lwuCcAaV5udrH*IY$xGbP9G(E z``jyw_#P_*ZiE*)0*&S+%_V2V1}F=H?3AY_X&q&1)J2hBf<Dsy9u>#<45$M?d**~n zWL~yuL~@!me>=tW8r<G_dP`!WIl5$W_a(z*=ntCt>dCFE$GLVU;)gvd{s%qzH17hw z4gSd|{Oc<KXJkMY#Rd?p0@4k?1A<jRsus{Xdv_av*8t*X?`a;<0&h^ZyQ$0hO|fdI z-fG&*qL-ONkF4gTh7_c7@rKD3X|<|GF3wRHBADG6H&7s%4^QFX=M>bL&zE>%gGZG} z0xZ4lG-$2AtGD;^``XdFrr|fp%Q8^CCf{V?OUgw{-~g0RBV%#!JhXh-X{T<&pbx9e zg=B6xzH@+`Q`{e%YX5rBH!Jhz$^Arl)-Rjy?wED@Z56b{PVHoMARg1e0TaRhKK6FN zFUQ`I0Z6YE>otj+E4rb)EgLGYkG1zyt;yZfuoan=hYlc7J3SY4^5(hdOpK-)s^X)Q zc)FmZPHC>@tgc3qapUp~N<f8906<@AtI*w~4M7E~&<R~zx&EH)0|iaa<i^aG*k{T_ zuLqu%<@3mO@#jxUANhdco4F#1>QtfQm%L0d{#XW=uWZ*4;mYzV^xH@blg%GMy(I73 zy4D5({dBD{tC9C&SnOpD0A><$>OBXF=QiCnr0m<4(x)>HX?``#o_KQMdh&S7;8(}x zu8WC^*cb-2;fZ2dhla)NDt~v`D;oxGxfY)*lT6G#-P#m`ZYc)p2YE}l3hQ7ZLhZkg z;3uq^;-_REZSkHaIfdZ*w<9wqp^hJ$%(t%F3Sa*qpZX@i{@&=T?x<Dba0`^hPqGkG zj7*m;*qVP$Qe;9{<OI5yc5Yrv&2T`#IO#b>pX4L1Z8%H2^BoGA#zsdHxsx@b>KWiU zUlxL>^9jWx<5yZO?3xiEeFuJY;o#>!_xzkBqs){f#g1A-kAxICx6EiYs4CG#h2fG& z?>J&2VKl?S0KK6AHB3r%S*={f7$)^1o#s8i?F((iL%{2n>&|l+A}j+$6y-NN2*~sT zr7z)J?+A5W>Gtk&)7H49C4vU`j^YKgbuY*8YEKx|1U{a*wXy*&fDIZ_Zbb}h-?y96 z=T6Gcg0|l7ti?D?XD)u?=Crz)Sh*O{4O!qLEMgkb`9Xe02<o}GX_3hB)h47XQ*xSK zOU<U)DvVRTH3KVpgW0*^ySEtC9B1qo2W2mbwSp=4;=Z)|`Y_BC?3^_HregGzJgPs# z<Q0=Ay{7109AG-@F+3_mVA=2(Z8b4p#YkXb7(S4H<NGNO3k9!b*$Qi)a8H5!#{O3I zLUigNw-;MKKcuSShK?cR9x`m`tNGMTEYzJ~W>{IIZBSqVDM))dZ|PRp<Rw<F<D2MS zUcX@bN&$}1(SPAKugep2V1ovHH#cuykJL5Ub(lUI?&zJFl=yIXdwi$Mj$hB#j!6}5 z+c72=OmA!H>FN&FQ+WNYULkvRKPPne5#g&r5tv9e92kNUo$SYikh8@_MM-_fixG~z zqixaNN?iF+3{D;Djw3toob0Np+caKT^CDFFALFe|E)kJ2GYDH@1OBm$;(p^T$j5m; ziPD^)OZhCFZ`8ggPn@f1hAfRX=2I+%Fsl@MqiY>qo}|n6wfzPU<~&Q)q1};7B3Vm< zVr-p-DbSvcE?7LWo6UCNDD%nT^!!{5Z*jOx=8~3u)`hHWqOl97znPd(R0FQw^XuZH z#*Tr5xS*-nP(=O2Npoz?Cm+7#mg{$is<s~FdCbLJxd0)2>K+~&f-eYnWD5MCS<4(k z%=a!42hyDKmYluYwH7X@(h<6DG(=l>?8rfT{i~wYp-?E@hs&sDRXPdA(vvDB@Un&+ z4#Vq<s6$mc5y0nKNDasiuXGb0hd(a=+*U*s9xnb?tf#B-9dVL6I<WR>r)n2)FF(Hq zIZHW+w)=IcV)kk%$TAJQb<FE`7($}i=1&0IkGl#lkh0FHgiau@{yK8Wym<yT`?ni! zmp%jQ{<`(hYPvjgL;EAFDN;{=Nz;j3FIl&8E;-pz)Qzlg_TezhR)d;k)g9T-+Z*10 z{s#?;Nu5rVRvV@8a{)&=sso8iJrq_$Z61;<w5T`9#28o1wt~Tn@I5|WH1zQ5v+N&H z$&a$(x3T%j`T(@-9`FekG0$($f3Bm{?xSCQifQ|1O!`3+Nl`evQwrY#ziG|novThu z%F8_vz8^Sn$Sh6!Y_MV>S(279Diis%`H?(oH?v|N^~T7iS_QEs(1Fy0SvebeCGB(w z#yh=?*3hQgY>Zl+Xf){{)u25)#U4;<k)=%9hq`7*s@=a;9_ZEXWaLy1I$|dZD~(+% z`+}hKYuzbdJqVxpRK@gqG#(qBjspEODeKCq5)8NTI^I^I55;IEzcPy7Sls9{pf)Of zyVBr)x*4wfgtX=yh%|#_mrnS4_LJ6Hikpo#Et6B9Q=H(+0rijKIy$%_CpkR8jf+i@ zZb|PpfN`fT&C$DNwIKj`5tVNs<E>49t;C5KHZOlUj;$7sH?mh;4lx=!R58O)latCE zx>iY?F;Qu~qR^EK+EjHz&q#hCSZu)M0;Rqbectow`Gbt~&%}&izzkpephYEBdP9_a zEkkW|9t0!TbC1O!rE?ZuO!UvuV^cDkF-<@$-~s~<s^zs@uJsq_d*ztil}ZY1v-IiT zOz*rE#m=uSJ-g6NTGFr$l#@BoUO5)3GMC52_7x&Xb)e+_#?H9=lC7Mk3xE$pXG|~) z((|d41(G>vFaYaK@<1r@Ognbg$-_<Iu*Uc<CtJO$hNwRlR}a}ie6sCw>d;=Razxh} zXG-9hpLkIHhNtw9OYWwNPLH*do>gBrp-<cgWTUj~*R|g7#KG<44P-FzIi=c2=isG& z;zODIE4J2;%Vk9tacoAHH=rx8-owEt8M$iY2aBVWrO$(=->%DgoI&2xOh+sYY6iQh zrU-eo-~9MxlYOaG`fCE!B)oVl1~quVgfoKAHILfIuXYq;eJ)kSk8&&Y<82*=x{R?l zsVPu*K!Z9XU_A`+WJ1+$;85NI>!WFGof@&<S!bJ30oEHYo^y)pRbLsr^?=&OdL;(= zz7$qVtuCx^(i+=2NG^}7u)rxrK5}nNF-{f`FzwX<5flcMFE0iPt}0Jklu-BC4PCjm z>h5M-;TU+2h~X%jdLqXcI0F6fSbnQlzQ9`yLmeF1>D`;On<7uU2z`@Dg5+kLS-M4B zaL>wgaf(0dqGK6sB7B{rfe@)(ITni)FQyJr{vaG^Sd+hM#*>zouGa%xv*H^i;kE&6 zrp>=u_SRaiZheyhwcAuW7DJWdTU<jzo+q)D4t{)N!vZz0)s2P*FU?`L%j31*52TWw zPmjtYL|juRe<M^LcI-}al+`Ar^+V7FI?!H)&)f6Y{Tr5rl~B{YtYKAemM<bj5o_~@ zW4~`Q4cW-*=PYjbiNvfZATo1rGM2_z(1fVc)5v(Iy@>Yk&X3r+(-ypEI`E~9c>SVm zVTP{usGzJJs|7E*Z6OJ;0pjxlCtuFZgxz8F%i@oV)-}aUF)y>RNjWTj$+h0UsWv<~ z4d+7^jxIU*srYOtQ9549EL$S`s)g}e#|$M0y*2r~M_RE=<F3UucGo7C;LH=*A3v(> zqj>AqGI5hSX7fL2beQt5)F=a^;6z`WBJVybQQ-TSkN1I}Q`(XEorB5U^%q;QLf4h< z`d&LQfV)zwRe$Q6APdQfXR@|3rKnh#%EI<?GC*${_6yE$(t{&ONZ&UUdHm6Fl!w$0 zn#XOVpE5A*dzy{_18Rah9@Bn!oP~RWTopbvo36ucn0>TtDjYHr6@S_&It?3#S9VLb zB2o#3(S?DcNzfxZjZF~MXcOaUsL~Ia9j(eZxI0l|78IOYwtO~Pq)2GJN<tn+A?d0U znQWkJ-yuYgVI<K|Jpjlxr5IS=3iyurN-o7c-;znUaK-SuYu02y+6=wj3oIQRzm2HB zT8?SS)g3Qnc;j~~*teB)E$VaSSpgDX6*mtdu*@v}w{O|zqIW&#=T`L|&+j_eGd;jt z-1jnJJ-4~ESGRz7nAtz?ATXteZMIyNmlaSD!5SD$uOTgB7vH}nH{MfmQu6sEJwPCY z`dn^KK!z`Lgw2-*xB>9WuV#DHB+}%^Br9UkD{Q?<PG#0<GWX*<&Q2TT46X#|m|Z}Y z)W{8Iq%B}XR#q4V2eYs;>atpcx0Cj2`;8iT7mn@LTNx{Z-A24KPzq2vf_wUKpSQt` z{6$VLq-!j^XdAO)yx%EfJYtiX7j-X-|H8#^TPLVRN0U&BxtK!tC>s%^DSYg0RXYz> z;xXE(DEX4QDG7*B4IrrN)f=(l(O|~@JMTSplNL$1P_#AZcxmWc73(?%eR<Q$F_p>g z(NvDsI80ZP@bJ!WS~mV=eXksr<VLzP3{nb_*rgRE&qFbn-^lj_C{{kx<w3&t*{?HQ zg!$$KKpU;^TlMwuUtSgGtH-gP852+%hjjS0R98((A2t`|-T;8-`h9g=rRtZE=KJg` zCP&R5+Cz*$;3Yl3sPCJC<w4T}Yts(Jj{YHe=QOL=B@7o^E+q2!!P^X$1ZzOPy&Elb zw;-tF2Xr9Xb0}eLI?#2Ab%x=?aX6N}sBvslJmqZU$^o;?THs2`<@EHirB7c2zOEn7 zpnSFI1a$MorD9yQP~4j<_W0V~K;?<N?nS)Bg5S5r?=#)s7f@SA89v*oQ(B%Sqd`(1 zwejl8rt)UsptFd6wc?{s3$C8J9`gH&SlCp+V4RYMCTdL^$W}9<*mdZHXnj$)b?77n zBP{Bs7VzPD*p{09h^Qht)#O}5oB1p;c&`?@Ga`}8CuRl3<gJK$qXQ`AP>L#1%{p|v zv@@tl9Xdf|{DZTRuk=}7T6QS}P>^mUhf%c>T=AGuXzt#HgcbxO-Cz=5lId--67y0G z!gKC*SlCSss<_T(U>`-U{;-$pFfa_MG#$RF*<s^i#UF>&HO0FfvE~*aL~lrA*F$95 zvMvam3%z4D6udFvyOBi5JA2Qkdvae#@0mTsxAYY1r7m3z{3D4YpNZGbYd$|&GeicW z(b3K)W-&9=*3gh$K<QncH!ppTXg*Qb7?Hy+lfzR$p<R%cA>@%SRzVFbB?t0ugmdJ^ zXxIy<Q<bi`>I@;OoE$7o`ZCk^m3Ah%zwV>$PaB|a)I~jmINnM4mhOV;9X+8aJLhAy zWL6SPURN2(^dIb8|GN%JfAQ(rEiQ!0=piy@^O1J&ud3khP|?HmSCLG9<4tFzVY2|R z=lfF7r!l4~k`d_MqF9Y6x3A`P!n?MrlKZ12K2oVb?xV2iD`z4U4)V`HO_@1XtKNqz ziP+>4ta3@N@!GmRLtx7Qw<ftY4~qo@_nHhj+c-Hr7A0An<N|1JfM#&9A%$Z98%~>< zVSD3-g2%P>w0Rz-WPQ3zv<MWtKHW8~{g*!x&i|>9$^iQH3Xs3D%Tjg$p^txwe$X&y z{6xg^oe=C-mO*X3pu0{NK}FFr(_f_<RYa-0q~k|2>i|MZ#-v%=XV0&25ZioKV}X3e zoF(zdysb0!pX=a_9n<Dwal-2(qo0c0G=C~`N3}L+F9*HvuRmL|B}4q&1;q#<4yjBb z=SJ^s@^fD6u}0@)aOuRW0V+>00lk2zTwf+?`T8p$&*PeWlrn_uE?p7pl|y*BOUg=E ze&SeATkprg`G;fdB@5Ku#s5gMp2igsX1KZ(CvZ}eS19en7J0F9Tr*cJ`~!nR5}mj6 z3Wk6Qr&y0$bD>MXAEI{rTMWr#B4cq6jrbD|@>5UovX&lVsxnf$UhawkhP0ig<uz`# zY11!^oU2ftU;)%6LppxiU6i~bow%)Sv|dfXWa*(gT=d&0ck_{6<#9vxpn;Y1TD`ie ziIxeyaa%7jPR^1D!B92CY!ifr)S&Awf_M6QArGPH_MK_@dsfnJOYJNZik)1az~3OF zIWPRQJ*H;F#F<6vDEV$#@4lmY41t=|oXde(ld1U2VvX~k-NbQATS~DyW-deCaCq|j zCn4b`?AN81OoyCfoIw-z3PKBDcVh2@XdlWsP>rsA4SJ=PiZ5t%!WcVBSHFr_V=Og& z9Np9V#7Ksxe=)jqPWf!oQ~aEZ)>w2onf;j2el$Qkp@yj2p9o9-?AI;YWB3&Lk=#B? zSmIT8N=QpX6{z3P(1RVjq!(z{z!Z1&oVZH$SE_jqdmhWAnO)pF)-kT(o~?+oxomQb zaPAgq4%C~#4LZ+{9b;J5ym%T1sHruE@Ox%de^bZoc{?TfE#t90{-lOaM5S}J8vISl z<c&Or(Qj<o)f+&;-e>#>sELvM&kVa(X_;9#l)#@{;e0q><!8B2CFd3eK4R0uWLB7e zU8>tYRW0i;trK1Y*EXq<hlFY`iwc?<(JO9eR5rxgdMHSU%mh|!b)xYiX+2R2Bt#?g z`GB|v84HV`(SRrY4%`ZieVM~QXe3K&Etk{Bq{M9lKTAZ0+Q}KA^N|)ki?C(KuN7{& z=&3Gc=gzZ(-DNc*X~B3A-OFFiKh~)0e{_pdT(8@RE=#Aj$s9cJs2(-*uYEPa^mPy= zc6F;4HNas8BkFw~9B~Se&m`K#UEvgP7ePOlfn=#ZVF9SnjJ(W`J$+ad@2#yD)xwX3 zz!@p~XI9Me*F<cydXT(ECOSh&M7DA%=8R_b=rE%BR3uprIvjIlyV-ngyJ4c9gfH8B z#e*YNu?I+JdN4M5-J3(+coV%2^C>`6of<=pUUC<bDHd;i7(U@jl;>fS;KqrKJM0;% zvWWYN4`qa<SjVF0VG8~3ISJf)@|VQG#`>@9)LZ&D%`>BTFU{}!Qhc`Rb}t$d^bBU( z#%f1=T@Slau|`0BIiHu{GM_#g)Jw@5IVS<0m?K$KU1zJ%8x<#?cDX1yph>ljm*|V; zkA@nZ3d8w(@Z~hJ^}S7a#k6hDP<QsOHrf%sB@j)z0OyQJ#MBzpiR6hRK)G5XJd5cV zase8@E9SFS8oA{Ypy77EfdhHpT$;=%W5M+Jvwz5ohZ^~)+s1U<V*4jVh90IXj$sTt z=kw!r9Twyg6AmZLualJ?gvo>4Tg<Ut4Mz32jghEpt|{E8NMkwy<XR@2#B(M49n(M9 zjb%^j{O%~{E0Dic0oDrVpHZFyczY7%I9FvTDw22O<_^ALRN|R^Gq*e+Ep6OS3An#f zwtssJ04ZC(zr0NW6N)xKH1038xC~Ks`$#><gUqZ{dsUPanfAi}FIIe=e_8SUiSND4 z05D-_-|RdsN>(TV3uOp|@_Jzi$FEc;Go36SVzslYv|TK;_~ekSKI+HmTs2ol>U%f< zN19vlyIu#9&Q!pgW?hsT5cmU0lM;I>Y!5)(c|8A9QT!AiAE0o}_j?N;AMo+#;-}Pj z3)lH3wC|VLse4rGKzOYbm$S28wTuAAM@YfP5Se~Y5wqY|%PDq}H+l~;CI<>_D~1Ep zP3pX>a9InLs5+INqfn8LU;^et{A$JK51LYdf$`d-<@fWy+al>`0BRf4`RV6vD`MX* zuR+{f-DA!y*7@Y*KnTdSviY6VuC~ks&&p&bgf0jE(GCS}9-bM~HN6|hcKraSdghpm z>hZ-7pA-b1{JOf_YB!K*|8+uqH944}Fp6IK)SfmAO$bD{Gs54$&d(9_CB{L(CMQ{# zBgSU9=#^8PKN18IV*~8a%n&4hAw+fR88}GZ`+KXdE~`z9^hS&6keb?Vj5XJIM8hn> zPpZb;8{p+UDggMffN>7+^OF$v7ePz|JF380B{08~c2_E;d@MWOusu+(J_8U$F`e1J z={U)-uT55Rb0a1|ihoOvEsyTZ`o8J;04ZJ;ouD#vwKil^Csb5WoONlEf7Gd}U(g5K zXj0t_Q;s<B3!!~B%u^2BZ9lc-02=p|yax4=42SW6;{JxRu`5ir!9yM_II9DMA7Q@! z8zSH0x@dw?3y(g#^5nx=-IM-qN8Pzu+pmdE7aQg(;t$DLV?IsV8^lZkhkF3hFfz8C zWCd^kNczAh$Cw3)1Grs$zyI;yM6c(3SD&e=od@m)o*d^J>&-4anN>M{Y1concVyQ7 zFWTM%s;RVV1EnZB3Nom45Cstd0qFt;RGQRC2}o$8NDaOBSdq?9LIeb)1`?78gch0t z(u+VsF9Omf^j^FNoo_zp@9W-mS*{hs;hgt9`|W!_`*{W^KHsTEVNvVS#;>>~7DEd0 zq@G3$LoTu?3Yc^gk(pMLR+j0je-8Cxx&_@Icm$1)9t{E=0KvKGfw?y%=HNzevfbvh z<^Crft-D_a^56lDAoAYu32QL*gly&3S+QMhrFnzVG$n5*Rsi%A7t*WF54lHD`5G10 ze;R|3OOwpfuVu98$zc_g_t4-^9FxCFL@p&mGRMPad;u8gGs^h%jRWNyZ;(n#C)qAO ziKlHLSDRX}5wa{qB{Zg`dMy%=N?%S0B2VhkTs|&X1nDzz;-2*-<DU`Jp8->Opm#`5 z_M6yY9k3wJ^9I^sV+8>aT+;}I>f_%sGn+Az6X-Osi1s4OfO)wMSyCb$>PQ~LHCf)@ zRBHj9oed-*BAt!J73G+i#=is@C>SBdCQk^#yZ9@)Wr|ILegNe*bE&7`-hwh^ucLHy zG;2KaOAwM9xk;ssdBy!edeVEffx(2CuNo|>TatZw;iqP)YV{g>5AjVq%aRil-mK1F zhjbD?58K`j<5x?5uV;$32oH}*yOkVQA^rtFW4I+qdHOdeKl_wyT+Oxv(oi2z(!SKb z%ke`~n}!;QAz9K~qoscth$LGAn29Y0*aD276hi&yJwB72&cj%MuIX5u1@mUm0D7zA zF!tN2HN3+S+KOX)A8ScQjx@(_SXLZ10>eVlL++SZ6Bs0HfNsyRWD5}Nq7Ma>TYx~A zTG{TMqkm7xgS4gx@8Aa9%YS85I01Q9FegCBQ=ex+4U)c{91JKzKcIfPJ(YVLKl>s0 z((c!wNW>0<BK!R*0~F0g>2RFCZ14=K*r;JS?9vQ=g#Sj>`7GoMj&WH5U*Fdwn3^`% zO1G@{LsAWG8U;S*ix5H?@A6hN--SUqL0WJ}bQQKP6BEQLhR#`Yae_2nMUOba$)i}9 zPkc{W%@oEqLUi@pR0N{1m^4<dn<Vl2l<6bKAw`Z2mc)#x+Y6apU~i&swWWb8<}p~0 zZ=~9+YS}0gZ(&rT5KG@^TX*K{>4AGp&+DWOX6;=24>!wsZV1C~D=8?4)GlK`>^?Hx zof)9QoW|3k1DDQme5a}@qn&;QcbR?t>z@x((O-5zc7C3~?=QM4J~TS=t?_=%03SDf zwpLfR-hpAOW0O|@^@e|5zVed6aCeAH8sOb>KGk++yo>RdNBNafu_@E#|5CckEIi`Z zhYb}*UUq>0ddp6Y&Lu8rIAI&~358IkFNATB0$BT!+;f8`O0V7wVaiG#w%Pe}Lh-qd zA0gm%URlIF)8HuI?x9S>{Hl-lM=;axuqx`BGqPxvnFE#yriJ{--qL{hY#$$Gs1M`4 zksdB$hu@3R=h~0H8A8+>w~ol)+~sgC&oj5TlSX=wQIBb)&eZ?e+;DRR2&~#7>C33O z;9%STm85hP8s5c{*}Dx=lIjdmNaZ5A#O5Q#i^Lz|R4gxcg7pehEbkT#tLAtY^?T-3 z=+9RMT%SazMm>ouVK#67>Y`$)_2HQ3)bz%a?Gq9b+L?b#nK6BWz`@++QiS4bCc@0o zJBXX!r*mDS?w4y=OBlHMsc~Sl6QAQ8<a&EwEs(eNbuWy-7KN&@)ds~vBsJZzg%zK@ zn$?M}x6(_z{?Z3a)(e{vs}b`%N)EQ~9*$sI&txu+kEy8G4<<H;{&}-G^aXz`(}{b> zG9Y{_hkq^EaK)(S-TVvF+<bcaQX4t$!Q`;<?fshh;Gw>pX}iyv#e%o_m#^pF>Ur^Y zta%H{GC=TdGHM>tFsf=vrxWlQU#RunJavJs`OUzTgLHukOiv;9_i4qr9>R{jx95`J zMtD>8Hk>ulZ>ht_!w6zMazaAp<a0or3rOhvy_x2AuJp`Yv)9qfDtis4e12>B-OACC zmZ7Td%E=j(u3`ZU_4)8Px1cswBNE<Dcc`fS+FR(V&ZBf>(i0#W(l5grVU7z<Cq<5M z>PYOT2EaugOUo-8g^kr@$6ipHk{%;S&&>$;dfI&H3io}jim%^`GFPm92uhC(Pzv7# zRRTGcPRTYZ5^QYL^dZO7&7&VAr@$vbD*T+6I?>l<XqIw`7+yM|iz^x!8A5;Z2x7O0 zcmpJB7UaVb->FVw7Vi<=yq;uSDj1lTcWNvS(5xG@@$j|iVmS<RJ`5dl=H9qKI5%iI z?=bO=!g_lI#$?a~%T}2N@;|~G0B68%wT%Ybx#ZssCo?N~VDrgABG)<*2u7o~hQ{@^ z)tkXRY;;Dp|5n-X!`An>!GhR5d%<e(Y=o=WAG5(dHhX&b!))z+3I7KdJI`dBS$89s za6(X{SN`Dh5jsi^e(LL)xoQ6LVfI3qo(6Ze)dAgjqhmprt;~!@TTub;=`GNmQ4hyw zfpruLG~2;59MJ0sic#rmjNZ(6p?iL2%!q6*=dFnE_JTr3qZEcfSIaCIiSQkzhKQ=r z!WRxIs;hLTLtg+ira#wzKs^Ka@?`YIFlrHN&28>^W>Gb@BOh%nkj1yYajRw_*Z6=1 zk3CDs*Q{36<u-Yd?{Lu*qlrW^GClA5f-I><5(W;#x^=r1{FHSdto&Y0`!!hRn^=!- zIcV)<A;+7|uiI19@X;O=$4$ejZXN*#R<f5c8tGMSUpn4YR^h2~lfL{>EY(whT^UGm zKF)7rJ4Yys&^|BEPDzMu{#2j5I}{$Vq^i%UmCukv`W8#t_HNl<SKd)px}I4DwT*rM z(K9<v9mmn9O9q&9d1JeH&dW_r`cU0uW@h3xw)j@yUIIjet{(HfWF)EX&(2nsg2$72 z>ezQR{iY@jx_B;>-bLl-Vq!{j@}lj?ZXKN6E$LC`^l>>Fvz~WO>Ns&<fG)I_{{cfX zm^@L-#1{(~j1CJde`1`VKgbc)GnG+(Yt)ghatbQ^Dc6sao>f!hs{aJ`$r1#z#Va_- zFSU1~cTNz%%uuQalc!<0jr#!g4>ZJ@bvdMGfh)nkn5h2ac^${kL)jpg>}c5GO!O1^ z*sM&Lxrs47gZl*LMkoog^(1kXl-M)+_Je#HWl$;D4u0Bp`Z{ha>8s;SbHS^zZ6PJ` zB@p9*(45<=tAevJVPjMu3=MDo%LKnzfTuDMJRbhH1!nmseL`FfNWJ8yVSSRilwT8E z%0>NJu#{XY5h0NFQXQIG8Xbl8vNpTP2iV~14;xti(+2vp%*D3d)P3Qr_GxPMCwt;F zE|tSf6d({l8q`}uNAYF8z~>NJ^x`NY+K9NU60KU7Ns_!?8z_T(@D*u5_=gG7a*@k+ zG*Wa60hLb<caehjK!SHU_0zn(&;{bQwG{$a+07U0npZ;KDmJbJ8Z8Pw_<ZzxS1Ztx z=hJ+5Qi~WsI?`rAB_b*wYM5xdv1siBJ9mVa5Lh9TN(g2*m)vgzsi!q7cWC34W)4dF zY0T@##uRi9A1vaUEOp$wEZ=P*BH29f2T@;xub%;CAmD(;7c%L#2%<h;OTYWnw#$ps z-Y;zKlL_dl|NBj{$f-TQG>(GQrWDz2i&VnY!GV*@@|TlbeWwr3I-?iF7HZ3Yya@wZ zdWQPn{aH}ehtS3M==r?H)o3&6f>boRra|?%CBe4?byw(UGBck3XA9gq904rw;9nL< zm*crmB?*WxrQsBZ5;NtpI)4_N(Fq~t-`?PrCrcANonRQDiDz8p4lVGXHaI%&Q2(|8 z@)kBlG4ve4I6`|(hD|gf?*3*yv}Fj5`KDq}q-npwuc7$zzvx&K1mf$f#MI3Hn+Zaw z4Ewr~3HCI<+iLkPXxTeA{?ZFF|ErGluNMA5e^OON9nxPZ%!TzD3*(A-rJ84>7rfi0 zW2z4AmygD=50UE=1(}*7mOh@*_?C1=FW{O4`*{uIBcYRQ$UPWvBKRzHk{!u4MSB@3 z#D79I{`(1fGlt+v#*nj#wd7JPWzgwyGNbp#hJ}-k?>7M7UUEs#C}iS%aOIh)toJw< z)w+rDHP0%oRj<q}Eu%$OuwRu?U1&6ME?^Yo#ccV!`p@R-g6o)cRf@4yen}P;8TlUI zQ#;X-dzm2~p2mLC>E;J%)A--h*-n%^{p;e%j)Kb!2JuhR*iIZ@{mJHLANo&Xwg1TZ z7SEW@e)9McE=Uo8rR{Pr{r}AV_V?eXBMX10Vg0*8@Z%yiorQf<NKV^IF1}2COXXT~ z5<y_!tdgF4HDUn>h*@Z=>l+9~7eyMRuuD((dR(3q3&$Ebk)cP?%m7LT8(UqfBYdG- zt_Kk(R1lB%$n~*EFReFAxp9a77p`;~#Y@B<!ro7;r!oHxju5i3Ln3l+kD7@#D8314 z8BZ;6hN^Mc>fQA%yo(f4WS^Sz1V|(L7Scw6hF^}I3*dPutGrBq;$o3>R^<5Lr4R69 zdC6QEyB~6sZ-MOWA99o8csP-KXcE*OCZ@WdK4OcsccHlq|0ml&3k;QT;orc&T1G%C z$f<Tg`S5;A;n6DNG0^Wa({dB|*lPrAHt21BpK@7vFfu+Vd!#@=@T;gI`cje5i;dV~ zPQ49m_c&~8`CB{4{%dL0ylGZV|I+y28~@(Nj-gMHb!#Usib>T(9EmgZ>?;?-AFg{c zhym<Wk;<MmpTAl5-7YgFSrf;yBtL(Ur0UNEGzS2`{un@LB;fIqlTu7NkX)Ju<k1!| z3trq#NpWV@_{T*VYwHyfTWH?Qi3=aTB4b=>xZndzMicYZgTolQDf{0Xt`=M74MNm* zCm&`%+(TuPDQ1})64?DD@53&$JA7AL0QVP=CJ9Ib11s>EhgoR?A0?Sqo5`@`>eh&{ z5D}8MV1ZZLC1%o^5RrASmsCTNh$RtC1)WJQS!ib{1?%opGIr**_$Mw9gMa_;KnMjM zao9C*)KnW*)ax=DrE_T9zbekez4$2&*JeZ@>$HkJ{Hn5Hkw!Rpt+FDtwdv&08f91B zsvy-lnJ{G}<m#s#8d?%p0PKI6n0|i*crECej{nP3VD;7GVIx2xRmXb`VoYK&t>dZ- z7qmtES@$V0#t;S#LqIHPuiK|wfZy;u-IOV!UGCWlwtQhQke;x+XQ0K8&9^ioLCa8? zV;f<thqgJOtmixC^WA$vU)_wftTzo-zf(!>vy2WlFLET82S1MAFgBh9$cvx;0Xp#+ z*kk^K@&BtshnQ_&vVW!acz&FN&`zQ(D#ExL@313>4$Zq>Lr~sbs^AO=90nN}<l^1X zYhv~~55htW>(Et2vdfPQKI$2KIEqd~>be79<n`6|peKIg38H6WE)EF+>>kOm?xpq` z4BXMu_M20BmxN-?`|hC^_bky;Gdb_PM6Bfx91&@ZR<(}g70YsfMo>}konJ(~dMjj( zT$(Q=ojG;AN@~j<*8yyq=?RU;>_-1$IXeENPQ^$kFXlNX^qKOXAeWHgF83#%Uz_f7 z|4x;08)BS!4dPE%TqG39Z>0IViOUnD(ZRXK#9mgcb#PX%e{d98nq1_r)<98NHkJ$I z0BpXZqV#V6QCVVYQIAxz%|KMZKq}T;?Pdw{nXvZFEl(9drcdQ|XT!HwbR?6$qUy(J z66Bi-CeOvFZtSR0`mhx)=6^J3Kne{*=qw@dZ_L&<(+7f7zm7kv%}P*A53B{`Ey4Lb zjD7Ibwbf=eI$MBb_+QEsKYOmCcW!SY*7R{fZ-3r-sOA7U2F!x@m&1JdYN)EZnUTe@ zuWTW+uSc@quX3oubb;lsvFn{duT|GBf={X{2gAEqiJZ1}BW*Op!jQ|@7h;(hwdv2b zq?%GOZPe(pih)V~K#&C2=S?Ko<vqn$AcE)I2vL?%>e`{$T5UlsJbWwLE}zHLaNcw# zvKst|bWs<DeikfqfBaM-{J4cGy7eCH3h-S0GV<hoS603e$|4z~fnq629+gs2xhKj5 z?Dc@1_}}UR-b*qmh^?uUx!tNF=GF#1w=%*iJP`2DX+oIp3+VWZ!*zmhr*uU<e6=vP zR=W1XH3ilB12;Tzc?B3aA4h6)nyCAZ^aUZzmrxhc(K%C_F?)X}kh4m_P->3hPF<IL zrUxi^jaL9D5*;936W~?|PkClp%DJ86qTqzaF^fL$4XiDdCRhk|4?H$7Qp1Hyn|a9L z4G+=674I8H26DoElWw1vx&PGx6!RG1i8pK&67tFDoA>5Xi+P*Bs8q-UavNtoGRJNx zs2{%MvBVt&wUu?%pZ<fr^!B%>H>p&9r1$?+eURL}+kjx;*69snc;uAzSTPf~IiNx| zR57k#>&rFH^fFY!%p#+_XxMDW9!#Qa?}=5bZcA1!b5F`HszvM$rL1=a6q@n5q`e*; z6~TC2O3p@=LKlZY+65JNc}qgc+F8(P$g=JfJmg9DmnFql_sNktZwPz1;T?AG1KONn zqgQsCei1s(L{vIjg+{)(R0&D%O~Wh1BRr<i;L;bxx?Te0CiH&cdRZ>48}II(c0@i9 zIIAQ;H2nH?8gz)+(MCS>rn#bLbCZb#9j@z%ylyamF|Nxzy0Nu~MP{WmC+*?YME?nn z#YXQ=*wSP#hx_0=tZN_16f>QcQ3O#xBOM)sU==e|U0je>9+NvRlJQ58$X0Iv6FIx} z$u+9RpU20u6if1Vh)WxW0R$crMbjt3`}iOdQ80vY?T4eMPoqK**`Ip^TM=|aE(!;g zR~<LZ&kf(p5|ZgC5l9^hnBtCGVL#|2-)46E&6Bhpka{U0GYDiJq_QgKOlU?o-Jg|q z#kT^$n(PKsvL`I)k(w+6&TNVSE?TqE=0DCjwAlU9`7Kh^t+i({=Hdb4jBJ%h=s1>x zXV$@`OV;ugcR4x5gkZR*1%1$@;SpVv>!Zr`4Iy{ws}Ww3`9b?T@r_PSLJ!7o@0`vQ zXUX`NNFAP+1)f?l*~6N;4`JTP^-a$$Xr+524^#Swn{uV5i|-6gVDrk1;cX!0C+O&l zFm4_pAz?C;|6WumpM8+6?h$ap^=K0<U+llOsP{f6f2gykIIw6jy4qt&=}p?|7+)~K zt~NEl_V#uqbDhlYT+nFGP?_F5S~I{2S6xhMZA&;W{4mO}RT>Ae*r3Sn*ea>NAnY;o z8A&8Q?p>&K`QqMp+&kd^xqGB=pq|%U5lv28_Q>i<0HZMYyfgukz9i;{^IS7H=3QNY z(|$YJjdL?g`Cv(BcXngW_KN74-t3XLsM-?vZ*^TQ6oj0d92o9!bm5$9Mj1!3!&Z2@ z227J}UR1B2)H#=x8i(Q=2`A4yM*H%zKm9{K!qgY4_v%a#Z=A_u;lLpDUgH=GskrS# zT$dT2IRGVr18!VYK$CCOUU%k^ckaRZUZ7fx-b4gQcVeW^UEg=EPv+r@tpa+*I~_Bs zNvX=tUhOV$EF6{G-33O@a^im+IoMxE&Wh5zKiJ>*5Ry4m<@DbT>&(KDPomNyGKTF8 zt<?rgv)AXPwi^}T{9n}9O$}Ed4g9?ZqZC7b{jiNqugwb$)5Kn03^(6fa$T)p_k*K& zCKpIIcJme6h)jh?q!e+#S7b3z35Hi=BD$MZGzn|rZX2v9y{TF4JZ?bvaRZLxF3L<9 zIaz$}ofolJ=H~B|-%A?3=uu>-25QDy*a)GXy*b^a*?Y^mR}(usCnp=}+z)>)E){&f zuv=6y-bqmI5q1d1c<DD^`hHQ#QV&ZPmN&hL9VcNSrJ19aH#QmK(6{soJOlG8{4>QY z^$Oo1DS<R>aG9**zNI)$yBqfZVyf}j@Kpe3=u2A(+K4D(N{>#7xO|W|FR?RviF!lm zTSWWqW#8e$C|K<lqkV-Y&(G&mwuL}~DtrFKy|795QTtdpBs#X!VZyREGh?lKs!Hw9 zt{bjk5bSrTNZqXUoG>pdAh^6_S?5~37LcQ>t7|YmRQt02;JQ;|!BBMust4K=BCqw< zYg3YVefCLy^EPjZ-y3uqofS2=Dz-}Gdfh9<SdAN-1K%96SFBvlI+#p9*RaJg;i_yM zXKKbo#8Dzb$!Gc*oK%T5CJUVWhOSu;=XUknH&rZZvJ62}Z-MH3{!aCyK632HyMDm- z<p}O*e0q2`W7|i|VN^XgUvR0ExYcDKEc)O+_OZ0V^}Y&7zyagj>C*%DP#p%|xA_m8 za@T9yiqyJPKQ9r4B(fZb_5DUsHR6L})ae~g1|N-oM*<j|itc$$0U%4C1ip~Cs2yJ5 z8SP<w<^ZLtyr->NZLC(-chSmqXu9(zyk~J)y9~Q>h^uB(n{RQS<-&@VuYx}uSp_uR z0>(wgk8$C(qc<F7Qj($y&MwKJ_e9=WT(I@j>ufQIrKmcri$myJX?e6KDs_a)(%*d@ z6R$KsI^EE{quHcVzNbUTo(?IqFSzZ?|3%XLR@Y#4P?f)xuBj9Qj$<go3%Tq=bDnwt zxfBSTFM9u%J~Q-RE>HjGK2wPH75>?0<p0rU{^~Lb@VUIeF5g+ETBwv#Cuuwlt{dWh z{tH%Kr=>Mu-+1Nijf1p#gZTJ_?JfMo&Dk@YZWtN79iTo+$cT=c<_^-Pgv$*Ix&J-5 z=xFmw%3kHLJwWvc@~2gDM<KFrI88Wd-F^>rp*rdQ$fdXa!=H}l?7PtaG-r!i<VEz8 z9DJ&Lj#mq@<JCe5)nK=IiLIDK;qI{I7l@9svW}tiAmUHi?^Tm<i>;bqZjQz_?p~$z z6y`d`UAuqJ+}1y4?%`i%Zh-M(kB&NpL0BUr+W;ZNKp6Ac^@iUcoFj6VzEU%EN^t-y z#)AW`iY!jRrAxpk&F+~?gT|)SbFQa@jV<+V>zZOEgbI7)I?ROEQkfa&#aTAZImRFA zMb*QQx9YX<)e>W6^;^V^N1Q1BwzuvC;?yT4+TDm{uH((e>*LKw$Iyhbv$PF)i2)#q zP7v=go5J79PwiG$;8(HwYYu0B8W|)RvLuBb3w{yr+qmSj!*@Y_LYVvf)Vxejd?Lqa z?)%j*$fq6d$YfyS0q5kP{qGhy2Z<jG956xtk1O0yJN+M5xc|7o(EuBuNjNZ~oBVFU zAIaD6^f4}IPc%gLj`?IcqCRhNBA6M>N)k2&r7e|gqM{%W&_zg>rQYl<$bgRedt&gu z9myxcsZI;~#7ms`>d7<=+6htjD5>^XxD^4SgL;>65O_y#+%Aj8f+_<T<=;;v&EHl& z)7KrJ)jAvx!+@Q~Ivc$%GY!Mj!qs1o`G?IDRN{KUSHbIV;*+9{a1u4w176g*_<Uor zT+4&^phOziTqoM*RVQ}y58G>jfFg(D`t&zxbBKBF!U8(U#)wqN^u)jNQVgM0?b^it z(Wy#!Lh&kn0qh{T(_q3>PGfqlcC#6`?Xbp975()SaQLPBNwOKgcrqRSR#J!Iwpd;s zB3AVRWI13Y>c{yKG8Y;)2^%X~3vqSIa4s?tQ<%0#<HA~-)a|m-6=g}RXzzr=`|Y`` zg6-6WDnJJ84`s`r5|6CEexY*G;3)oYr71sf!u8eif6=D=rvT(lI#TREr78a*E=gT- z_5VZ3+G@wjlkmUwI{zVx36%Vloc<3T%v1k-tv?t{e$~PJ<5#`T|E=DHdTH=q>Ywl* z@}IANwa^DneOja7U&TXtewEauF5&z8?*H;;wW5LB*@4Up!jazSAWBw7KEid}O9ZmP zbO5`tA|Zn+F$-ygI&Wb%OOw~F1S@*XoHk>#37ekrG5`(ewp@$@#w=n{^}{L0A2RAE z0O2(Ns>36Od^!V!*}<8CAS6zohM%9+E)`}q62)iNKV+kkZcn-xJ90f$3dY&Tejy1x zgLj_@h#~kPuQJl|!7si2L0a1U)Jn&=0oP!s1JTR$s+k$w*jz{|2$;v=g04OM+Gf$; z+E=(_c}f~05uVg4bmiLKN#y4i8-iQPKHmW2F_hD=5mxBJ(!KCjG-(uc#b;(N?NMM( zUKgBlskXvr%u?@_Z<~i@o^NmfS2U~)+LM=8p{m>JoC}wx^pW94mL9xXOvmJ<Gyr)i zUkrePp$B4Ee*kB-V<gmH6!DkosU)O(f6a^il>86Qd6)X{RO_ZLdw&AJZ3#zsRsC4a zx_xLL_x{?;Z*)yHnH@U5-bH{~ZiHiDa|`>~-a7xc_FHb7t-_|A8x~s}V2w|T`>WLe zybpsXyUvOH=9a%CO7KSGrC}oN8@WphW_i0@GXlKVpq~PU=Xma_C1e*5Eb{5<n*{Lf zP0)WPskF>bKPd#D^`K~V>6S%R-PJUAGTM8x%PG6q1ljfq2tl4(^h%5I`AGJ*-oZ>B zOh&Tof(!UPpjdS|CLkCfw2hN|xgUyZamMsecI4wowaic839AZIIZ`t#OJ3W`1`B&y zUX8eOLu9w-6?};yHT<)vRJ>Je$8;>VODGcqX_-9FWJ-C`OSf9KdEMD%mGe6lcgX8+ z_=Gm!E&oj!__Im6ZkhKPC5DR6b2GS_DI4x;N9viiSwc6lhV?7c7S*WdOV{??_=-&r zKT>A;mjE<QxuL{|ZIY?AMI$juZZNIq)T8FmYPsGU5Q;nU_DBYQ+#>3_#*;Cfygj42 z<&u4$Et^Y(HPc_6kZ)}P-`;K9SLZa*F~_4+XY9zR>0Q(fN}e^Zkx{iNNBf3*Ns@{M zI?88E+^4qp1-^(?Bl!Gsha{5eHH`qZ9a8Q!jX1R@680Kc7{syQ1jZNgS_}4rzR!Af z(PoF|MTay23D)Jr#`oW;#%~Jn$Xj%4c?{N^mq}}L{M;8cOV)MHjo1CGd~ZI_K9Q~( z@2CT_2uGYb5b}D+5z%|S(!h4Y_O$}bYnzebE#v!vU8U(AqdL-P_EaG&t{%;;f|z(T z-fmWSy<cvD8on&(6_SYW!S>4>D&I9mrK^;&n9ZXc30*mA!hJ@~WJ3=>J>%<vI`79o zxUmRT$iBm7HQvB<&LOZf*Z()a;fo_!IA2j{UKP&auvew@+_=H};q*`N(?$`g=DLxE z^ZrqKD0?)&XWAofRSwXqcqhc{G5Q+Lzv9u%vIini<WyYpyk+rl7>bbEi3YotjjMMB zmJ~#9n0l4;Lq1-m2SYz{6)7~G<?w2{qlByo1q`^1oCpOD^Q~YZ^VUkgZ6$Nk19G+J zOr42{%=Vh-!~KpvoGhqf)BAK@`9d_R3C{_tQd*67t=Pf`ey6Hm_#pcyFx`aQVanOC zTn%+b4sl29HKn|>4`Mxd%$c+G`tecWG~h25>Dy#azjKp~t{Ol$eq5JkP7_f@QT&7L zQ|W!=<?_an`#_!`JPmIpQ}e>QG(vO98aJ=!p402SrS}P<z|q3*AXJ&uJO<?5Kb!O- z>jYg2BS|NsT3ZK1Anpe8SKE4w>ZTt999Ye76knf~9ktEhu^nzZnjjmK7UYK|ua>u2 ze6m)ao^`ig0$gr&0?!aGBdsMA8$fqQBLTn(0q!2a?*j)AZooXk4SPk5o7G-`HphPR z@xfviRaXoySrSsck~)*6Oy&c6)KEsK9gT_AZT6d*p#UFWctp3Ju7|}$3do;5e~x5+ zmAIcC{+-G`>ycF+xiC0-bp^n@&<m-lRM|xY`4b&j{j%H0dijZR!D5UR+c7-uy~Ytx zbk}|OY-9RbtiR58DxcAhQ^eXc3s7a}Y`KihEaJ|#<ppdBS%i|vyK=y@KeGEo#lu$; za9sYyito|$z^K1vu)C*4vdYgi0UQPJiaLD005TOB^NgxKOn@Q98Yy4*lVpZ$UgtTi zeZpSrkGYSJ7~X!<&q@+8a%h_v|LSKGe%8yW(-W2Xtfx9)b%eBu-nsH8VGEH{R3eaM z3WB1Hr?Nm%2dII}cUfh35;hFKg-fkC`0PUh`YEX|`h9(#FA=<PqC9UgQ$vc=Q%mp@ z(fZ%1jPpHH<%1)G`<D->l=(`fzQ$+JF{|B9{OS~f7(L5}<p__TD^P_(o4e0D*y1CM z$%_sfmN93NH2@cWK`E}V%_|q$W-uBm>iNy$(=G*8J2QH8=J1on`W}lbvNwW87@i6r z?ANl-=^x}D4Z;*RHm%Lsj8W82%lmKbItK(<42zf+O=qSjZQw0ej4@N+sjAYlmCd|z zdnhbVW4}{L5pB>M-gwROl%d`VP*9dTX$N`sn-tl}p?foNOrTSK`vlBP?iwXuRwp<x z==#AveC_@k6&t;`yLJe`(f|8tI6%t3LDX`QE+yt^1_|db!q%#Ah}gBdPv5D|s6UF@ zM?30{ta#|??%+(3UYZDPg^?jvwB%%*MYN@Z^o0X!v7VBAQ?j~FhO2jBgTCdx%w3+@ zHaoG1MT$vqJ(bf;|H5GhF{e^{`0dV{ubSa4dH{1#T3Idkcd9SBW;LguI5&2G%Y8Ex z;Cy`mQ5H5BUYQVCKfY*nVJouhxtArg#hb0f0Ff-PX$E0niLi0eRp5r}T@LZ6{jjM; zi%o^xnX%4`Ac0E7$S=zR-dy4l$@tEZAe+*d1>AVS_O`(DE3t3<uOP%vFvI<V;AK!3 zH|$wht^OfXC`Jdu!K<UYZ2Gg!s`B$VV+j6uCv^X)kQchNVjyIK*0{(!Aa=amr!aUD z?^iWzflf>$rfF&p*`Y5Z(1W~)&@B)^oH>^V!+S3HM1c6Gc2Z;L-BOp&X?2L{Ml7=I z1w7tYD%`R4*zHk+_rfSFz>w5%`@ETNpYLr(05rxrj~g}XXQNU(j28CG^Qs4aw%>d6 z&S3)`Rh@knK#6iDfWusC>fe;V$2%O}TACzYTFhK^M&Pbu7x-m&i-s&kzf;k5-2ift zbQ1vKW+0k#I;J{T>KeY!Ae+PCqp>NXuC>p-L9J^w+nZyYnCtyHP{`oJ9EJ0RyXGsP zIrn6RBqsx_nS-xo!XtCd&kAjW_irst4a{yvU+>wtRhvUL8;7_EteAtA&kj98mPONC zg3D~fwjy+w5VaShG}{U~KIsHT){h>Y1sZM32Ow3^_8*31_CJ0XK4??+QB~v2qd)5! ztPCguF=f@dtGy5B6P7@-RaJqYrjS*2E(^DFPV;zK<poq#t;zPb=k}%|UBzd4VGpTM zqnHtH5l0dWl+-{nKI099`B~4YxciI3wKv=!HCOBD>6pjAPP<b6&_r~E&tV(J&3vYy zXek7T_2{|A8+g_{pg;NKa)Z0rHUA2Wa%Gp$!MEbiz11Ui@MD|}$!F#nn(k6W&SQiL z&ZvkEQF?!$g4+X5%B<+jMl{6&-Itj(&^SC=AKkTj)+V~QVo3AMP`IkPDTP3RLj$6! z=c+n_Kw3Aq9t~KSVIX>P$+@SX(PHIyqt0WI=dP6EV#SBmqMY9s_m)T^1wNKbgZxZb zn_}~*%U@cVCr)zO`93d{-z_25CC(6zqMT^g8N5!Uwzh`*Ox`E63L<nNh87URg(t43 zf#r$nFZii?Q@W<Te{vbzOvrN-4MiytJeRYgdW35|afY2ir~zv2EO!4keu<a1i#H-^ zsrB@JiUk0hO=a<GyKc(GfuLT4Cj2H}cD32f#8D9J6->1Ml+Xk=Bq=R!+Mh4Ab!20v zCDhb)syK9G$#ElbjN21(4|RjQA~TP6cx<ux&G`48X(s>Q6wZr7nB^)z$RJ1lq~T%G zz?XGhpFIl@>)l9JH?-JXLej_5a8j!wug3zNMS<~gFA=UaoJFutVXo;`X8n-!F30+| z$j+Mv6>t@GL4ITItR)D<@S$_7uVMM={wM<|JqhHCpw#iy>>i1X&KI|A<{K%QmUI;2 zns%ykfn?WwF?P-ln~hB*4d+K4&R~wg+kKR;V1R|pFS_GY!i$h0|7L7TII-Uemg@uO z6WokOU@|RSMgZ(B+jx{&*pkf4iSl8met$Wqeupy-LmI<vH<+1Z?vJIRq?_~dSD*SF z{zeRd5dm=lFPSMz`$Mp1XtZZyN+jfdmsNK(<nccDE08!|e*38|TS*DLatnLr@NC|j z=|I*FX2X3EGe(Q;lFJR6(-sw;&SqwZy-f<5tNryg4jUnbWp1ak=KGWSg6_6Ck^XE* zg<L6cew^>lD`pbJ4^e^Lkr}aJ7`!C5h4jbu+T~4`&ocxlC-%=Rfwc*;_b-LM9f_^Y z?f{!I8*@WA<K?K3%in1s@W3D)^hRG~32_@EmSB_;U@<UiU@W1|Avb<f4w-BCr3bpL zx;W$?Ud@UYDJ!?Pi0$HMR<?F@Hf-fXRJT1J#^``M)<s?Dm)Asy8wjw}*NQ5M;<bgK z75bBP6Fdrf_QTHkrNmxu$7qztFmD!5YlSWa)y@8RW+-Z!-E`!ds^<BJ_LSrx1695F zh*rgG|20V}BKCwV+@XuZQ=u3))w6f@b;oL<-RL83kLiL8q(MNcF@$z%kJFS>E;}AA zl<B_z0AOhFFGEOvi}_Bqc&6)fL?C<$_L+wpwU@*=pRrITU)@D#-zO!Kp_z%*fV9u6 zw#dnrdJxS4wPx81@lFM|9lMY@^P&U}n{MTx{PhR~=;UoXNW2GTUt^e4U)@6t(iUqh zJRX12ExxqsKE)`xmvCDa;8033MR0o-jpS|{K`#xRjA3w*a&oUcxA;ot>Z#QEJ%o<T zmMm$CT~nk=!$x7+xpGrt`sA&(;@$LRqqW>1U`;-JWU_Yw*@ghN^ovx3iz8r<2Jx59 zf(Eazxy^jZcsqrY-x$b5Fl*g%nS^z*E5I6}r|nY}l<U1~cM%nO{aPZeE?(9dOYa`8 z^sTG)3%{q78yHqEj2U(n0q~0gVWoR;ozV84TYcMVgs4;wDQo7`SshK&D$4kPf&Q2@ zfBW!mx`0-Xxt&x8ayF5M%lO*4DFG*6fR<GnW#{YM4<vP!2?@<xSLjd*2q41QOB`c% zL(|^e{bw96chIHh8}_<b1?h?>WDYkZTGbdWPMW{HIMv!UV_R+7nzW_<ooe%y?HqWH zg0xQrkYmB%0$N+V8>s8|MyB!{U9P02l;<HH(dCQ1kHZZiHzsPs@VB2V;m<1|ay^$A z834WF8KY@+^BZM?)#Sw2rO&MDO*=TvaP`Cq9mqDu&p@&HJJr$qg2UeqZ?Y$4yJ0C2 z_MGxkcCEq+oyiGhextSTb91U3w35o!6)lk2M5JRP4UgfATP?#DSAB$a?1wIKmH-mi z*w%2OiL*ffMugFcrYWQ>S*F7LwO3urX|)$%(t>?iVGW9{RnKWsOMG0|YgM%KjN7#B zy5tykT6>@GJJnuY@rD3$JP~NXKED6?<!~eaKfc^&d!#eafG2{%nGQ%>9@=YgCkD~{ z?9q92R6uqdo>~7EJb8FGAmYz__G$IfhMV*xMC|i&WX=eK;qaw|A(8Hq`3{v<lm>v# zSZ-es2y5Tqh;+6%%0q{}kmJxGDy=@77du)x%AZLw$IoE1=+XqPH`#_1?d3XcKoz|w z4Rman63R@#XS39o7Z{sdRyt09ry@a=vScNY&B-*}m*!%F>$P%F74q)!U44ncsu81V zIcu73;Z5X+Hv!fb-((t|2Ll%61~kF%4!nV+zVSm<gFQ*_)5U(7-PPztT@Rfdjhr<x z=pr*#Q0T!+4hwOegU+bvw3@gUmmFf(uu;9%MK1{t;fiTBUeKV$0nYfEXuSM9j}*v% zLc2k&ZgNOg%KSH(cuE>i`^OA7VgFWwzLPgh_^Z9Z&-(sVU71f{OATauG7UR-shUU# zQVZ<vIW3Tmy>SqET;;v+w~q@qjp*+pcizzOO1`<Ud1|U`$L2fLf>3&Q3Q;wsgC%Oo z80wMiQl}#p>)&sTvDeUl$=kCU?lKd&$PEe-iN8G0My-w9enZ1&lN1{D>J!1)j(FZl zVDz?95+F@ww283G9$GcA<kL`k5Uc<XaXHG&KV+{klK0;;<*!Lxlyx#?xS>aCX~)eX z*h06h1J)|c(pBjGV^uZ(uBz&BRWC{6WdZ9_|HHaIcTB3f?Z(JnD7oB&t3A7{STFy+ z1jO}w3}S4#N49D|T8^3(`7DWsS0wi_-y=6C&ScS-DH_{g#+g4pmm{jJp@K41M#jxO zVkDmE@d+U9FAoh@a}J7kTws3yw1I$Q|KXzs=e*u39TS;*iA@fPWRG>{9-K;iC+CoO zr`e;CYVqC-OpXgIL)1?foSH`&DGLHMHs-)m?bp<p%);89B(~wJ25!>R0qLOKL|PSi zpp%z(-g~<!<)CacuyYB7+)#sRI{P-gCzLv$70TJg!->x)M5*pgJ5Fr7h>8}Cyere4 zyV{3*gJ7{sl>}YQdgU4(<&#@(XokKKV4mUK@8aFqbuN9lgW0+_ap&Q{@X6BfTTFTv zPUDO+g+~`X5D;TDI{#%=s<)=80A0#uhlHheB*$+QF9`q>YQG?qgIM-JqH*St_~*Pn zSBmZ++i6Z*hf}X^UQ6g1Y8~W98qZ@OYvU4+HMJ02UN@^j1+mPS?I^kaWL`|mdS1Q{ z(k`bQ*K9@+|A-?zZr%%Zj8GVA9JON_1hApx{HrE)nVAB*7HvK;w6%Nw<LE6hSHfry zotBnc&$gx?$JhCJ{gzi-K!P2!d}ikLQ>EFM9DI&e2r(p6KHMR6_=ZHoSC6Y!@gg1& zd4PjkNQf)<;+MEx)4cNgLjkFcwrzUrVye2c)$X%bZ+uHe*9eF9=BGRbQi-U3=E9wd zK_2V@l1~AW)5xwDKxibCu4-l8hs=5y`q`)2NzVjyMEs}@YK1lm+Xg9TsromEhvt2o z6?`di<%P?LtLq?ORjig?Ig=q6o)ca=Zk_pxY^-4_<e}DNpxQn+l2_d|4Yr@IH4+sm z3kwiQ8W?h_DbiVyW|?R&BvnN0+bw;bQLE%ONHrrvlr1KQ;Vn2<r`e1mGQ^M|)zfE0 z0@4W2Le{i|yCba(s?Db7!j=r~@3i+H2)ms^<qV%Spa9E=WjEjGlXzuQmU+ls`HhWf zhf>x%n3u7sD0kb_GbJr?_T@q^%t)%<lQ5r6n|ZSIyjX?1p-557g>H{Iy<W6{Y7@lA zP^HzEeOCMCTnA&Ryx-O+Q?`h=u=NcXZ_iL()-#^gky%9O+fGa9Cd2|mN+4Mn>pz}4 zg{V|QE~f!R5y1r1X+)LW14mI*AK9Q1>R{eQ;J3bDJCj>_w?#OGR*x02hT|wwLR&Lo z=YTPSq#3zzl(R6pIl6EW>54bcfr!wDqKB_~Seqw6Akf!*$Ry)0ych$#1iofUVM_z> z`5tG8;U4$p7$^4?7y~(CZHR?+)la@k<$&0$36Jz!#rpseV(1CF;}5|9^RJ9Cfd1eq zEIolcGCt(&S9fu)esjm>8MHMs%fu;FnVbKFX$JFrr%E1%lV0z|##<duUfSG#gQTR> z2%M9`Ykxy5hA`~)dftXOA^F|`9c620&hmx~2yCDKenAVw+&BF2Q*>r@X=;4EB;0)q z#Eb(+F=p^VH6EpUI<(vXwO^|`vl7e87ysmjW(1k}YWkr*^$6WcB;_!+_z)oC0G!&n zUZul%ODqefI>Zwpap80ZA35ZV<7A{c`lj76FI99g@kOSy$S{{=i)%HN@Tbndr;ZnS z*H)Mqr5|~&b0&S<KEY}F&Sf|P?d?3yTnzr)W~wC8T7rv5fY^pNTtc>mACgu*Vi|H3 z)iB-3#^$D(#V}3=?wN6BZLeTk6CQio=_vP4fv~yeC`%2d=KS_3GixS&!|Bn`PLt8} z`opY=3xPZFRXRbRIsMx%!BcRUz{v7`&_>_T!;xvjC*a^GsRdy5IeR{$fNXe*2fiw` z(h_Jl`n_%QG0G+p4}I`(ItT<?*U?bh7KUQz8O$pD%x2c_&)QaVkd?AxRWCA0uvs~} zHIiU#Bp_>cE;KZ?`H_XpQ4T}L5g&Z&eZ_2!;8k--jvW8MpgH`%_e8(3IZln~5o!2N z1q4}Z<A(5yIJK4E>E8L>+e&xGZ;h6ZFTE|)3I=#r^FYJVU1$BW@tOV&3nAhcdIE~U ztnWz1!e&Qi6-=xcm$kwbxL!_*PsM`HUFVuouD0B-@5zcK#hGxgXP5eJoT0d=GFz)` zPSv|bXB8O9n)PfgN}m~lj;Uav-rmjQ#)@)<+jpdtUw05kQq><#ibaZy^h(P+ED}rQ zn+uFIvsLo4i|?v!yy`*u%e6Z)s2DonALFL$ha=@&ip>eN@vmP;3wUBLplZJ1vhu3s zqUPq8;>U!ppkKhRhv)U<AGKGzHu&!w>=AD=ehr?m=j>358#Y}Q#fKRfEMF>q0X2h+ zE`<lY;M8rf9C4Q=IQU*2caxZ9t;A+-DMjmYm8rWfqB48?bi}-&Efc7~Wm}kz-r1Xt zycL6i1-fCqa!Q$3sWHi?S|N2d(~G9p#^*(Bz4EIaI0iJZ8^5<$p~LWL>U_}fZE>c2 z#Ii+5FV!)^mruv8cIx+0F@gf?W{m9hUZ~hkgRIulxle~yZC*iYrkDjug-F_)4F1Q2 zrQJV}#8-!%{x>A??Q6&sHX3em@;96Q&yJ<t_T~j8qe7yFMCY;ATWFM($yKbXv4<RI zH*3$zG3wZ;y36$@eT&4?#2Ucm#h7IJN3j{RImG2vr^Pm}d*yQO#oY@_hnWCFwEr7p za(gs*9#DDFSO_JJ&dFP^&*1Ng>`XDvyl+PvmtFLJIdofVYr1yL#|v!ruIoEhLa?mU zXu9S~Xj(IH63H5(CoBH;Ii>3jKvk$TDUn#{U#s-Z^4ZkHFp&knty<?}2uOU9T=v|9 zpgP>Bd1mez^WykMW8vDY%00&SlJdhLo)PNFMyp*V9&ikQcybikJV)<v+?6l<`oq|I z&W8;Yn8E3E*FX4t<HGCa9+jmI($ijj9xwhU3z9;6k`De`2W$xIp$E->rK%hLjP2A@ zQYC8%2evD5F>{VxVXuG`O6USuoErhL&X{YUN>}T$O<j*Aj*#s;n0;fS+?(|C?wN<1 zk}GYJWjRXF>$74z%J)bqh>mQ|pq~2OCdMV{$63$CG>nk$K|FLV_KVv+E}Iu`Gu_7> z&5a~x1;Mom)5go~PM6y(?`CWU_t`69&M|ie1KdhusMX{}*q1VuJ2S*p^s@AuWk-ha z6P7jo_>H7fx~`i9dF%GXcy)bpXJ<7wAXOh-Q)7%SVV!hfzZ&spY1X+|waKREJ8)eG zqpd%6d|*ij(}}JhPe^r301`*47wuac#Q|1B5Rx^QMvi(Csh0~x-_p*_)_I5KLxOPo zKADh1%qUkoI806tp53YPm{e4TsrnMNnSNoB<!s{EjJ;^bbYPkuc75epzZ9^NqVl)8 zHnVFN=x<-vc-w^^3R|!)R+>*3<h>EBQM{K9JJ7*zzG(1m>QUNL-qB`snh?H!FK-c- zQwg!EzT)VbdAQ*Y2Raeu8z*ZC(XS&eMbm(+Jys3u_a0Piu2n<wx0x6{B{mf}9ZVa~ zF7J0(k2AxJ+Y+nWhgQIdz^s?9_4HD!GG}~~E;?#U12kM%us*L3*9Z1QJ5HzRr+=_r z21KiXe;+spKb8(Tf7(rn%3X9e^{J3bOTG5KXUz}$xDqb+NsBWCBJLXcHNMAa?{;%4 zeGub$)9rc&6(dD)Dcg>7aj=w&oLSj+qx{0qiVd8hwywXekM(L<yl_a#sNrz$n(t-w zb4_TTRWkdV!WOP5b?GI;x)MOIUC8spsZZU><LZ=aso7-W1p`x;K8Kn0kESkpj@?Qq z!x@JScUw5d_zVz7xwzj59Ba;8>yeMcd+aFnyE};%<okD++C72EBv&a2CXzmHI%?*< zQEv!qLl`<6^LD?5k?an&G)lQ1*~u=+V9@@`s=l5PGD%em!mkbbHXViXl8iK$)vrf; z8&h#UP(KM$VRluAXi0e}OP;^~H42a)L?@~9qTs5kx_No-R6nCZYB3Zr%l@Za*}bVU zZ}hN%<3@jT9v^HDI<Fug7n<Jz=vZm^r+k2m%3>DEs}2dhN4IT-KlykqUDrqaR$eBR zc1foG7EY$Rys$Jw*#5^aB;Ic+<xNp_<}CrTx>PqW7JWJaPV+P2ax1aeu5ONk!}Nfy zb9Hkl)swheS)N7J1y)REvvRIcD$5Yd6VhT2-S~;Ol^rT`yIdc(Ud<}4w)bM0>;E(a zNN*exq!=27h4d5<5)E_J(7cUx;tPEk&-H8Aawp307;Iyk(VL@8-@zQLHXV+7Xeb^e ze};8OALSVbNS*9tW{Q5XtG)owE?v|b-T>y}4I?dX^u_0KA<<W#xQQ#}m|@6f0Kyvr z{20=*II90<YFg-pkS|S^?||TO6>-Z2n<vtIyb2H<D)EWx!9|}M<=CS`L#MqZY`)MR zi>>~2p#qyLaoxR@E&`Xz(7UV=C)qCdclB7Gp;NmheFGala7jzBK552=E0h9hW7cDa z=F(@4aj$&ooLA2&Ps0IRGseab5=uKdb5QPmLlsD-Prk46(D@^2t|PObEHEL!LBXSB zSj>AN1vE3NV|mx~!n^z&^sAzJDQ9iD7^_(54B!#V%)Lk(-R)Cix#U?^$=sSa<&1k( za<Rm-SmvR%ZCFcUb&LaA4h^VanQ<3u-~lS1uM|=|jDhoH*MpS_Kg^Pt_ch@KA%e=9 z0@j>Y)ND`MeR_4fOm2B$sjYV?qzU6_bO_FMCY34Z^rguYg<4I{-$!2w&}$FiN@Zqd zzDQezq&{(y2QK-W8-GJ}@#DND4j1tJP^xtWM~$(*y}7`huNsRv;8!<r->G6zwgF!) zQdvK8VH*>;_?Lq>if3L&oa2=iX2G8?Q=Jp~L@=3y=I^z~m9Rhytb+uEL<8P_{XjPq zNmfdYuj3;EtW+2AmM^y#+R`PFedTZA3Vbga3xKm|E4&Rg|3T(Wl*#TO*X5um<3~p3 zO1hSRU;e*bwaGvD7X7<o5g>&oESiqu>1>{$T?7bs*NW!%Tmh;^^uJ339@n&9bnDJu zOYK1cG59U5Jv_uA{ns1Z{_!lTp~up;vrk-~ZN|fQO*{|f0)H*(f4ll98TD&n|ELD= z&Oho6|8Fav&u4h#t$v!vL>3?lL4tdczgE+gA^(4TFNgbp<zwqk^#K9gY3tsNX`mu^ zSGu9kSU(yVnaji1KgeX;WE;8Og=4n5)4r`>8-j`5NH%63?)V30+yU<mC*Y=Wzr|Ye zNLaI|Vcv#TsI*?{dYvX9#<2()*^&(;yoK`kY-}ic7&tr$-xL0H$raHz&xILMDPL&E zq)cL7yFYu+2V}$3Kb7u^5zF7h@AO5iCahWj)fq4<Jd%2`hXCO(SsH9=S*~JPrvxXf zfX?tf)#||h)TWSIN$n;`iFDX=>uk)SwboI}ezw!f2gxFZ6&}uq4sWD_5dF!?CPHk3 zoIHG7p%p$c2H(;y1l0b4#A9jrt}EhR_g_q&j6>t%LZDA_WBCkYn*sL#z?~-^zVkO3 zih(eamX7)k`~$S|1(vbkRzlILta<+O`|*M_r-9+ghk@OP5nO%i*5OBa)16$ly<OzR z4#vBA89mHHou9zLdDGvV+(#Z+o4svGT&u&)nq`XKOa&7iU6%3qD{8WFPWOavwl-cS z@|Vu#0@5^XBO%nulkWiH8eruECZE3!@4q)AS7=Y3(m(QWk$KV2d}Nq<7BlB*y_H!H zrr0`phL1|l^G%1S_((O6snnrug?j}yp3utfja7pj5Lm&F>g@)GeV5$hUBv41om2UB zT6U_Q{=xoEl&WC}DcneX1*Babh>kMOnRj8DAHr%lge=M^$g>OcIVrD?hLZh|irn-8 z$rpw5(DPEsFh${vSQlss%P32)Ju2oDX%Fd!r@2IJkNgu)!^b&E|ABOa`QXZLT3ki? zyWWxoV&;*ktHm9xJn6_zJk5Q%UzN@L=DWm+facn$)mE+BQTqzL-Ul&~8v&rXTRl=} zKlnrO7txFrq+a(4L3j-RFE_0BrD^?}c;*XR*;C8BDf^@TsRN&+gMP{{cYguGX;ik2 zWcf<Xxd5rX7^s7(c?elMDVaEJ!Y#$L0ollo)yp5LQKTz32Q=5Z{gbEka?9<8K~~j1 zc|3zPTFe*noqks$!=>xIv)xdJ8?iVSixq~P+oJO>xm}K_C88<V@;(vi3n^U}y-IX6 zW^`+@_mbi)rso>b8#nJcp}2=9G1ul?PR3XhHrmqNq^>IZ`FO(16Q{)PTatsWOQc@N zpm<k48{^z6nqJq^$}lik@To0H(Q~L6^(k)=6(91Bw=RH}TFAVotrugj%&IKUnUD;@ zYM||AHcT+mwnr}tOO$4<mK3A($Cg#|n!G6U{aM}<oS_|?1S5l0xlbaEiO~}}S4De< z19T`(IB5@voOQCw5_+n2^GRsZ^zve_YYaQG`X;r&P1;kAJ`4en`mNNABD)ST$8G(% zRKliDzn<FA-YoB<t~fK{YEevUL~D{6FZ->gDkRpDhF_WfB`oJH1!nt}qD<AQ`GCQ0 zN_I>AJx^;jW-mV~Svoy(d5{Ol1wH;=5eXZol>7wAbps$d_KiQ-*OQtrBmXP{a<>`W z*Pb0}`{1WwvPV~_i|ERC|G`8C;8Om@M26u9qBf{yOFzN~HpXCi8wqOaIDkBd`sL2> zUV^ObPSkg*=z1A8a8zx+vIjhLeCM0c>w@t7z2-slUry9{>%S(L>d_X;sKnpoV1YA8 z{F*~LN~@js?}MHQd6V6_EU$fo`soMhZ%=4w1seJx(i{E)5Bi|#!oq))Kw7lt;>9#f z&mzJ}o9n!PEW5r4>hn(z39724a6^K0v#`YyT9GB+j0_bme6CrLRSeB|eYubkSpM*C z^8matzsQ}MWN1DD+ZK0g2<Ls_lWTZ@4tfM6TdCmn$B5Gv5tT&PCZ|x|_-(C=5j=() z$<=KM79o&Y51~OTfjl<4CrNjZe}ZY4kqQ@2@WihKumG=g7tR<~K+yej1f*b`UDB6a zI$l6-H!{>S6C9QTYx}HKu$}_~>|Ux1;o(7(RK0`cuyGt4`u}0<z2n(j`2KO#UDQ!E zYWA2#(IV7tk5#)yq;^}iLPBi`;&j;KprlsROe8{$AZCZHp+<~YRkekxJ^Q<QpZk8! zInVR_zTdAu<aOm5xh|3`pU>yLoSVM+5-%MU(B=&_hM`OChIgZCyJ4g<2cVFiP7D-- z$oSU%niOCWN35Da9&FE8+#9c+_i^{85e4zW8#XnnlIcarH(f<C95$%(eS;~(RQ6&_ zTyUeBI*j!fMb42LLp}2eRpz>w#`K7~r>18I2IVj5;%e=LfD|`hA?$7D2ze9pl-Si@ zoP2SjwNF?`e$klsU=RNndD*z=2xWENXBO0iK4eM~&NcgwFIuj<5tZG3WBj%(&c-|h zIA=y0QN3$&(*YuvmyX&+-dL&0yg;>dfLuFc0FaYe^oBz&FU8w8Y>nj_6N#3aq`;t3 z<HAt2SYexCP%zk{D3l%E@vdorxOJ_Ki%C*eDxhYUhi<4(qUQ1hngO4I3F0xpTJ2F3 zzWU24sNgTOGRt-Ox{7b^Rm0KO6pl{aw1Srw6riv=zio9(qi(2@CB?NJye#BmU+VKX zW3C6%?4Qz98t**H+cCWF2z@lLuRAoZyfqwC_!SEUg|XX^lRCe|S@wW0ns97ix+qv( znrUh$r9MclDgN5HG}e720f!)7%wzOa8!6B-VbnXQETZ&w$G)oGVtjM|P$NhF^{Syv z(Twz$FBL2)9J2}B8QE-r9u-eOOXC0a6Z!`Iy6(hf)MsK|KH01nUfZt))(5kH#+;wg zYrhWH(Yr};NPy<yC1rJV6b5uHA>YIs6>JRyi@_tIAIWz!G^&F+EDR<Kx+gr7R#&m< zKIM{SI=y&3nMDCsWHnOXYiM|CyUb=lm_FR)sy&*~;`cHIdmk^{>pO!0D5Y&&d3Xlo zTOOI^cVVkgBDipEOsd}h$~Zm0)bR9PuaB>tJK0z7iC0Xun;BO+f{;;a=PSf}8SO<o z#e+J%_Sn-eS^eKlM^yAA9xE}sV_+?@-3>8iq9*G-Dg%nxg<_K}9fqN%^RVla_J*am zhN%jR7CysV1_Z@K>-5y>MZB^-vr(+(>Hd!PIvwq$5}l*U%9@Bq?-Qpi4|(X?-sRlX za?n!r0oG7BtJxn6S<Sq_aPIL526Tkx$%`pDJY1+D4Ylp1>PnC4siX7k5;d$YE_?Wj zVGt(k<sbXts$|KKYma{yz)zB`gaV~1E9a9Yc7=**_Ap~rw)Ji-+z1IFQ*7MX?wxF` z+3y*yrLmj9Pp4?iFWG`cX{j7O#F(H=VY5(Ng0>@6(C_W|X^O}pKTLHNLGHvpd?dM_ zAvrH3KCZ1T-*NKlFJ}$b9#=vyfNC6xmm!jR$^vh|0~K!6&hds)9i!Y<^!b#Ng0fq8 zvB_%VyHhRsly}`ZOd9HMZ!qIZ&c;t<>U8ZCh`g@q>$JE>VpA4=<o{k4td2I}J;8T{ z&$BwG(fz==i<eRd)$3N}YwLIg5mIXCK;PjyrHMMbA^sDn9|Le)jw2)N=E(4o{kx&a zAwC1^pv=<2oe{4gQ^gh7`gIMr<JJU^;EGn1rO*6DMTX^F?0pgywz(fbsnLD@@OsH3 z{f3$LGV@j*pJ$n&lee{m|B(ITPaVO(NaBE*4e*Kk)2{bSK3tHyZbomt>}pZa<J<vG zi4vt|IeO`(g2{j$ni6@u79pQUx|x>ggF?S6Wt3FdmkS87c6N4!VGVjDlt0B`BeN(q zX@^%5e=|?XQF48A?(vg|J3Lx-g8v1Qu5qJLb&NN-W(R4DN`6F1%T&nFCBw=6E8Zth z)PS_nPfne_g7R2>^F`j$eNxOb^H)PBQa6;mSK|&5cb{jWbI;4%Sao={_-Vw#L`mEL z8UyeEs&fkm0`-aa_7dIg`S}vCF2Z5HLgJE6?ZrhygyCG1l-D$Pfkpc>WM-&9JkOS- zWT!%|Sml>G-l!YQ;~C+AZ`9RafdBO=t*dv>oOsG`1I;gZN)!cDjX=rxrwUjv^=}EX z!ONyne=D25>BrnUy`P|%Ll4tz4}K%o{CAh<JAhG8LVlCkVz#>9AewoPSd@TRT%Q_k zX+<{Y;!jKp?+sU|+P8@F9Du#QxZYd8Q=1{MhQpU$>_ZLl%Ln*#<V{?@)Wn@>#%{*x zM;G$7EAE;H$(8!x^APHWWkFf}Hp9;vf32q_7Y*QL(FAJqeG41H0L6>>*&BnvkAIZ8 z3YEb1B%RLfG@pz9bTX6y+RmY}W4#zAUh6o(;|m_ArxA_A8a@Q&7UDy^$AuDjoSj@A z$%uG>S5cNaJ<2cN+c+7mv$m9RsIJv`KM^nSbNKxeEg{A^VgMop?i7hbIUAnJS{%6@ zeYq5A+t*dn4auuwX`+Q)^{g)^XXaK1jGgvpaTp#imIk~8JKFW`FOCXlEcB`5i9aUy z5}7O9YtH0=Be^tTie5|5xS3Y?N>TeadYO`(1gc*Wpubt!f>F(LEeeflVeult?N6_r z^?DT8-`8w4VG%k|%|ny2${yU>hh|jduK2<|4CY8%_wtc>zS$A;WqIT_=9iDc*sC+Q zSf_jMz)A)eS^O*dF4u_d?j7!7uu_xFx@p=*yF%PbaosQa4zO6FYGSLOt#nmfrM{W` zRX-rH_#;U4%PdXOhs2f;m`TdYwn;GB$WH=OtbQjKp>rA;MNd%Ct&NNdC_C%7tjxUS zjdnN9md@9{9#=o@f-NnJG@w<bLd~?;$E4D1GDuKiyfW6Bjk{Dww=)0ex`O@@M8lzA zkh4)73(qLpGk>`~2D-d+p%Yzq>(@t3da^A;_ze2nGwZ7|OE8nXfw6k;gqf1+De&;Z zLH0Yqe_|5WK}_$6il!_ol4EMS>ouI0W^Wzl)+Mh@L}6*t(&pt*_0NNxbQZW3$*#oN z5=Ub7wb+hk67fKjFKKsN{fI7YV!Xz=?Vrk$Uw|RfA&TcFgz^IMLau_J{Lw2-jDpO{ zvZ!y;sJ-jWQLlN>T+IM7uj>k!l_55Ve>u>@?jZFCdJXS{#^`L=M1q`)(v%%9CuyRK z%vJ&&FTdZBIIM|179DLGOZmaTuH_!2(oCy`jZ==M$IPjUQ`o8&0$K4iVmfIQ`p{#^ zrp005bBpxiK)U0^mow;`X2$E+yK~xMS#Rv&hq~TVEmrtKKgiM2a7Md%N8;|2d!U!= z=I?Sil`fHUK6i{?LGLsJvC1SLF&?5X=Imw;1e&QB*^efA;IJW_L``^c9mJr6y6UXP zrlQXm?*;hum)ePgRaAXzRh<0&K<Foa&UPd#)gvj7Yc6SA+B+?HTuh0NaOQX;2atJ_ zJVCT*Iyl`@)H<=Vy@4+dXrfqzyz#JAD>k)2k%;_;LkZ>HjRhzu(L==a`B&de%0gbk zyE>bi=ceL7bl$$^lQNEsL|;5{yIY&~w2-8rVlVo*G|eE0iix|Pz=a2y?ILNjdO}i` ztc!bsosEIbX<_^;Y1yNrIoC7_CMd>t><u@x3bt6@ZCP?50?V#XdN5@kQQ~IV_-1vt z^qgz$IvXG?yQs+~<*TEx;wfXTT!r`pPj|0CiEDF+ri^qbIa~U=+t<5z%<6j?DhJ<? z`VoYD`#79=mxs&PG+zN6c^3?2O5OGlt<IPd?VpwGhS)9SdvFG08tsLt4Lyz=V2xb{ zwYKFWDgQ*;QTu^>uSszH@V?cuAn71++co@ADbOII;;{#giR+WDo%0bHS0te>kIl5H zA<60;L*+ow6Br0Q!-#J*Kac-3!)ob0TC>meP*JhlsoECCTo6z*v~wCJa-2ynD9npo z=oj*zMc~W9>=Frw&8BaOy*_i!1F^8fAzrMwib3@S>$Gm{jNLq%B9Y23rXr;7K(#a} z9z>lSgr61UQ+H?TU!V&Ili@C<f$~5Z$oXP82HmJ4DfMNa$$M7Y60#vq$em3)m}Q&- z&koQ$489!|;n~cjye`6a?h&~1)L+tD)jZ<x6-LTj{2|6MV@IW9@wiGQQ+jD+g?lE# zDTB%RvbRV~PBntW=CDdwR0bn<Ft1OV-}h@z*>KGXiiE6%u330#U=#Z{=<eSB;OhpB z^oaPIOu`G8XGseYoixww%+qyD>M_Im@YjW|uBW9%SR%MH57Ns26>4~Ko$>Y!=3h?E zL(!iAZo$b-(|GhX^AjTC438SwiJr*B0vOMZQKvjz<!Q3&3-+RTr=*8T${D559T~3L z1HDZ24R*j-e~|*Xr}9iaVt9LpIUWk2;d=i;!=0Rse=h$QI{ue`Y8YV$0LB@Z&-S=p z{g2IbIFJkocnblI8BmvB`RRVj^ABOwBg#MQu4{lh#2&Hc2z3`#d__@n^4Fg|r4vJM zn2MkFpN9~>wC;iR*n%)0q&_CKEJ9}oQhi!^Eo71&xq5U(e<5@}-!4X*wwEX0?CWUp zwTnrB7MYoaTz>iOyTRz!Rqt7XG-CNd^_rD6Agrn3mxm`QL(~!t0u=8K(}qbDM-R)+ zGtb;6P)@~aIkczC*tPwz`H<<k4O>wE*b~x#N?yKBuYBtFcWTDn*}A1*U%PBufUA<q z<)Ki<9-``jv<r-vZQK{C&d}Cb48bPoA_aNJ6XQb@pt5g9bPWuzjsE=Asig*48mP>9 z-xGfvR(QA`MpRWkyuL6szvS2}DYAVk=m&~{ignd|k<EK}WVkoGU$rv-tuh~#Q;bqO zIT{W)`4CKcP)sUHd;92(5C2@tyzcn7m976=`G4G%_P4dD|8e9?$a?~sTkfO1)2_Nd z7&Og~gIi^GJR8{R+;@;Vdc5p1$hFO=cp$u|^V2WLX9j-8b~ZH$M_W|R8j)(cy6YSF zXmPK%$>l<R{pDQ4Jo_@Hr!|i0>%&eB6bn+8kj};VRcR-4?q%uDvHL6W2th)~YAI3U z`CJV?|9+{S<U_DrR5K)!h%OqLXmCl#p!wPuf4%IkcRFUnK&|m3=0y018?QP}oiR!K z7hEek==pz#Yt_&b+>C<q$p0tnseieqdZpa>r31ir(Ry8sk||9>zofi<3aAzNPl=!( zNS*ivJtTBO81?pG;@#Y^7ssS@Ff45*SLsr;xdFd(k?4kDoR`VhOf(Db(WZ_*n|*g5 z-+20GhLn54U)ZC<IEGXqoclVoUhVhAe74yIZdu2zQ8^bEZ?`~xYaC5h`1%d1?o;&1 z%^sB(sAO!u%-=*+oX~P68h!W|4u;?C23ve4%To!cNTE$j9gV|s9W~Pkw=oePkj=KT z+%O0L(qs`l<g#005v|40K*Q&hCmNqTtS4yHesewA;=%h3Un%GU<P#i8p3$D|DwC|o zU8Ok*PAJX0mej@Ai~5f<fk9t*BKkXZcj~J=@wlkg#VM-B-t)~eo<QS<W;0mLm3@|g zX4E~Z`qjL_H;n^}9v{9DC@hYSN_PJDXB~Ak*fR*vtaifiY4tH<*)R?x@Z~L@w6yy! z(F@ym+f_t8GQKVVdMI3R-@Q-EG@`v&s&ao!NPpWrsD(BGyBII|@=@l?mw~;{zG>na zxLMZw<HCa`=Ef2HxwKGw^3G)^pzDAVT#|LS78R1(2z-vbQ(Z0FWCqZ@sqU&s?OelB zOo2^2+lN+_QJAMgx#dz!kqh3_^+MIUxa$ie(!#J%W`M^$BNFCVeJH;OZYF#kM35?= z%ZJ~EL{cE*Ts1z;hhl9D`Hn}UZy3-r{HLy<PTmX3j~~~3a<=-=j@I{}!Va5w8-774 zpC67#?eKeMn`!*6^ge*2iyB}2X{_?y+Un3fFL?K|G`<#;RhQ7GF2aiP1K*m%qK^wG zz>I9ml<S??!tm-abB5HtDLX*PMXZ9qnH-f}CYI#_i7*wYh+|5H?D58j+LOxR4;av9 zmrrvkyZ)oYSnT~rhrtZ_|DWWdb-Ea@qtwz?LZv|HpI^)hkwC`sUrXWtnB#tX8^U;` z$7{OXEI8B98xFYMap%Oa^bOW)k}Q_h#k$1Dg-fwmD7d9UuWQRPP}@faf#_9w&$;Je zygrcebeSkrU4eA=wEQM|`C>G_UGHN<$Goakihf}`dhiHx=%KUQ6_g{+x*~ob#q;h+ zzZ+a%S{uRH<(2f|7ClQowk*_FKmzMlbT}4ahjhI(wW4?!p=TZt&dti&u*E*$7A=a- zBA*gI_o2sFt7YfpO|+TBX--{dEy0K_+&U*(&mvBlLwiy_>mg~F>BHNM!NZT^f{(7U zHAn*&!p8jjRqiwAe$M!{t^z!+Q#Y6y|26VwG>wInDS*(i0w)C(HTn*FrUZ9sn`eJ8 zoIfto+JEHNaOd?0om^KdcAt&Se6v|6mg+YR2u^qKMuL86BG3Z*>hz)4*iT<E0?B&s zwhpUk#>dNl5WV=S-1uSR9vbE-X*};q&qro=N9L<YSzK|io*OXhTdl&~EA~kSzsB;Z z&B+#psQ6ubahFn6?|CBJrKIF61&#d3C?LD9n*vWfe1I^Vc?}SVYb=^mpZ|KRU2PyP zER>Rv`M3;wt0v(-Ii#MqLQD9uM-=*=)CpcAo`aHGaa5kb=uEK=^AWlatuzcq19v0` ze?aOgcgLxsv9cx+($rb0TWw&6ey9}CTq_Qx!e_7b+p~L(*JYm4628`Uf{ju4MEEZ$ zZJMg6fBMa<mHt)Zy}b$fyUBa_?q7{=4#Sm=vT7*5-s4*L<xGyvKm&aoo+tK&pZU%E zUk`dL7Q#>NZIJ>rRCZ0rc$=B`nI7Os6VlRPq#wNcnYnrYfcXdWox7uw?nS80z5Hao z4q~?%7>p~`)~jD$ZD+AFr3DN;0L`X-%`C2~S4U*51wWnkznksvY)jjljtNdBwz#!v zQv)RlydUEs2&Sg144;^aB4^~IE=njukXy$eJ}ubu=gNSkZEcopY-meX1})amIZG?y zj4e+i4{j<F2?E18IDEaB(OEr=GAPeK-hv1bp*iFBO<!1}8lMn0cV1b8fWQD->{7$( z`H=72c{tC6uR!o`koA>*ZD=V%XiFoKqaIvVQ)Ujk_^j%FTE3R~@;Q<~WvLC0yJHb| zg=W#;B;F{JTG_Sgm`fUOh$^)zx;!Gp;PPV4<kOl-#LT0a7FcAY0n7RT0u^gAK*PBe zk<AAv*rhLbGe7n!>w$2y@vHN?oAjwgr3;Buw;9~3+v&rc2xPoX<K~1qrpn^}4!e<P zhFb}`Lo`xfhbceghEZ&!rRHV2o{ET-)I1oP2eQEkzCe1tWPh%n2_I2?p~COM?+-S1 zF8s87fm@`?R4SEIvOrH6m8^@O9OX#V)=o)vh^ckmeExvv=$*+sX#7)}K^On~+JRvY zm{Vv86_IA+>ib*QQmI`_`$M^SPtPu_iGP^fD~qzy0IVZi@Jc4K)JsvI7hc_KJTA2$ zl?}O2X!W|p=XQC?x#I!0fjspJW!zwZ8JGIOrP~b0feT0_z7VNTD7Z$an~8ue*(zQi zNw!xg#lj1)MKSTIMjrQ5S|v9mZ}JRF@hu$5Jl2x}^X2rN2uG>=*PJ?NeDK}9@7q8D z-H(QLkbcip=nRtfyrD(YsIN(iOA9QINh&uZL&W8PP6Gey=Hcrsv9%&Pl>Z8`7MX7v zK=1_H2t)8z0~V;cUO7@nnKfoI0MwI{UNVQ+P&)r<%7<Yveqg!`@}Y@O5)8wjH?ldZ zZjR&NB!@uLOMMp7KpNRnc_TdpzuG#wLYwV!euAl`D@Gz~!BgrzR0MmZ0sqn`+gyM9 zJEoT*JO(z)qR0v1*=S03EQL1*NJ7Wq;vPfsF$o^1niU3~OwxgBi*6ZBdNyF=1Gb7f z<4feIZd5Il_O`gr<C9^dpXVfoi%=}-SMIOLkwIEi0*O{Wq+0(h=_f1=Sr@GpRUz~V z#oveP_5BM<`biv0G0-mL*y5O^RQ9NF@)>f9GA;ok)EMW32Z7o5Wp4BE`l7Dbj&#dR zj-YPcAE@V-1HU~oEFnn3(26=H?c&fb?n~ku<i%8YaMI1LQlpLZN28U<7kaa&2$Q6N zkxxCeIrrL9jKAqnqf~xM`HI;QK9mLDFdo(Ost3WVaQhcv&KNBHSCfbVg#Trm#34<O z9l@1ehSr9^zKi&JBToK3PAueSFw4)e@B;?dxPK9?Pa);M{;M1M6%=)*+56v2YsmkR zY0b92X+kPr)kv!Hm_SdO1I}3^pt9nU9CPk7ch1GT&S-sV_B^$=$OEThKt4OZD#}U4 zJI1Lu1bO!rckqgrfGJ<0`-J)rhCAbKq|{PH64+jvIcrT@dl}Q}@?IQlCms!NRI+)w zSk_}4GIV~cQIK6ed{-4&dL4<*T|pceTZ#;gZ83wZma_<E^se$rEDGc^EdG>q@1VVh z{dsZcdk+@Rqj^D*FW)*IT+u&wO70}XUt#P&Lx6s!RsaI7YpjJIjEBx_B_k<mx5mEA zBXciIYkbeV>Y;au$})a5P1E2b8l-fpZ%cdNXstRMNMlBPc?BZb9g@_j!OmW<9yosU zw?Ll8)2P$^2Bs7IQ{yi8-}TIKuWWoDr+s5STc0fqz&BU^ZL4wUYqL!d81%J!WeHd( za%1m#6FvkKmxcE4Rq!Yx$dC;EO!pY^yKA-pue+OWSDgCR0Olty?RR6i;lm5B&*J*W zXOF)fd<?QWX7tPX?6@X>@BAD(TE=Jn#xP*I@xO0zR-r-bWrNYpwVTtnyXX5+zoQx) zHZF2Bhi8lL3Lbpyx=E4!rBIfg>EPq7KM#vqlMRl%F!k0X<&gDg&iU6~S+-tTb{6Dc z>scGx|9q{}#~UC9U&9t<!xq#BfenE_kIHq>x_0B=F9v9bKj!oGuU`PpvFxwi*7e?D zFRZIqC_Pe$AwVQ3w-Ze3V*89fX=OMF%dKlDzUo)8(58((?yGa5KC00r!$hkSRNTx$ zLz42u!lt)x%OwM`Bj<oDk`vj6twYJJZl&mcIwOaOI)(}t$ehTodjnVeBQ4Jmv>%^p z>4g!E(MiN3j2&jXWVKq+W^Ii8a_YKmu+THzFwRt3w|#%!tYHy-B|ioq07^C(1P96| zHL``uo|K#-r+SvBjQ?zFqEhaZai>r<Q7G0}f<i>vaKME|DWU?sZqK#sQM-kfqsi!W zYSR#zLw?C&(*cL#3v_h%Qk81)|1$6<hDsU(F}v#&$$(`-HJ9UjAH}%h>lC`Cl~Y3r zpN{);6hkEn@z4qH;sL8-Xx4GxKvRO!-O{6evW4<?2F1Ls@KE@Ik0Gn9ltdXTYMvUP zu-}snF~?r;n@cwDHcyoE_`u&753lFebgAtfYwR5CB<(K^_>S@S#HOaW^P5b*56&XL z?>EB55uD!OsV;n9jq;xijtIY9lX*u6^x5uH=|pPZOUNtXuKqzXr%IhTK8$>d-jbwB z4&``1gO~IJz5hOWZTHt|@DdLS_8Xl(8mn!BDazQf=2t9w(+^^k>%u-#=@?13Q)cV# z<B8MmtW-MF1rykGBQE3)f)E;Hkrcxvsk9980NJAIX;E_Sy02u|oGvK29OWw{w510i z&({i)m!xrh^%90s{l>$?+tSjiPCog`Nv`y{Fbh2B(wX^@nZ0N4YfwA`E2bR#kv{mx zIOFrRN_AMMqzt%Q0$ZGX7jM%7-WOpKs^<sauehmFFL9(e)wR#^q>c4Lnjy@SV<<JC zkZP!KNhHajgZR<L`u^92IRXYBlYN7Ho24v*+kM(uUCk)wS_Tk^H>gX(z0%e;`KrxL zai94RwsSJFv7wJA1qCm9olyOQp$wLRDEE*^4j}S<18ZnQC5!l9Sw{x>Z-slNGxteX z&9Daho1V5EZmO{%s4Qil$CajaYt6jYeLM0XA*pXWX@0=2=djd5UHvvM?WErJ7cYsn zIUuQOy#C^fXO%)-9z`FKUszHFzPXZ+$Jy0(w<H?SxoqmpqIwhL9$#eJ%^0T-Hp3ly z*V0~AS_iUwKyxFSY~?4bIKCMrUpH?VCii&V?SsfHCExU>cH5lk3qKD0He9D+eo=h8 zLVBpLKD{cS?)<XkwfrLTDAsfQ8(s*%?TIwwfMCIn4`1D~`vO6LI5#~KxOS5V>H<<6 z7M49Bcw`y|<%e<d$wjoqY<dD@KI_kxmTkdVF>O-nR~0{=-Dr-04SY{{qE*cXFZyGA zqL5@;Sc-cMQ}60lF-nX!<7?LP$b2!kIM}6nvn7&E#VZ<j%i>E$Yra^6!-{Fs;>4Xo zs{jjXyP$hd=M^Of=esVeclWF`pMFK!g~Rp)l}wi!fo$hr3Ap~cWNL8C)8b8#SIv;# zJsKkIp(ArltL5YPIr#lr%Pwb?>{H`JvVY7xp51YgRA~*h*YT+>9AX9K;h-e`%l#WZ zx|@)4j#5iYh!WP4VN)b%v8K#rk))#-g`3Fc<;*Mjx`EVMt17luQV}Y@a-lhUyyL6o zd;eaPQI5+RanmJ-EY}-G9kPXmR8<VirByUtbMaTWOAzU3SLQ1(X9M@Nu`{LKX01N? zaUpwfjHPR%Y;gHWRa@}}moT)kwSHh|`vNc~IPP4X?cutz_U>ItO(0ZpsXA?<mN&Cd zfnpz^HpJ9Amyw|V9Ut8>>Oc*&v|RiW4p^R_rI4YG(k=r%)S6IzXl}Wjn8|Xs@#81v zisE=-he~C$DC!)X>y{n1WWl<I^)^)cj&;H2am}0YwSc9drDf^CKyH^}Y=n`NNV1r+ zT^ku-Pnxm$yjo+}jKF^TW9pU0uR*D!3i}+QVkHGb)eAoupyiVi_14J>-#i{i^F9|^ zh-qYb12Rb0a5*fZ{?WcLu*{E1C^G%Fv?!Amy_0v31&_ABdWs7bOl+lc8MEh>A)0!N zBtabDjQFiikD^pz4F1+YhORa~CRSWX`9Af%a>vjtdG5A4+utKzgB%2Rn@0ph7p%ob zmScitiUr-(W124y1EbEaxwEoG(O-6{E2a|yJAY?Z<^PRYh1&f`OaA$1%>>}F;jImp zaG3&{h5%@8)@@H>6frOqE#;PN#^u3tHvuXxkMI=-FNDenf{1HAisD{@XU&_q>`7wj z)e|1sr$Vzl)HYX=mWGb0%2~Ch49wxWdA3T;QK7A0grn@E#+K(^)qvd)Olox|+<EiA z%|@KZK9(~gU38T%&;in9J|HA>qtON9j5p;`I#K<N{lg)?-{>DE=iqJ29*tEEL!)l6 z<p;3jQAHt)!S18f0Y$5ueAE~nZEc6x!4CAf2}bFY&bQsrw<Z{6Z^)ZGpCY1gw^qk7 zf^hkJGRgh><`HXNP7+C^^JY@+97~Jxi%JV?i-{22N$0onb7=GgAPWn<DtLxcn{m<% zdVwO2-D-@ON7h)hL#*NXwI4vnYH|;)MSzlY4lo+P;iF<-wg-P-$?U7F%QQF4_nTZ^ zSSB8ym2xJTIl8+N9$~>cFA{;sO)iOqaDfijcMTr#99Tj>6-C`PbcXgfktGN+Y+ACJ zH^KZ$!sD@{d3m#_k@rpw%M0<nWA-1h#e}i=cLRtb9`OQyIcxYEt7<M8@5K9At6)1B z`KfSLBfY4hdY~|dJ;7G43M?-i-E_1Z47pqrI|1VB2Z7D_;!^2gxS7SRcRQs#H3l@l zWzvgb3mdl;=@+RN>nyF>`GvF(ebili@|EpBj>~wHjRH~Op?`2&gay5!Q7~hTj>Xcn zWj*^Xv5e|MPn%HMQ;bSXw7_K0qpx~WeUG7%H7sCDqdZB0nny}LolUC7MM{#r_8w`r z6nqSRM&GN{*#btY7|>Cu^72I#B$eP|hJ}<>oY}7m?EV>Mqt^7-yeJa)a|!jIVNy6Z z_HS?2|M^~Jwr`2}(|oaylQ;f9=8HDXmAY+sJSIkRz6`f9F1aF>KVq*LS_M^Yt)3Pg zs!(|~@mqxtiRdbsPo_<hx2_Dvop-PP-T<MX+|pFGr)p3wL%nxN*@ymOIoXEi=RLi~ zzA#?7GS?9o+SvTY8rAg>;5nwB+Ea-&8S~d64G}6u{8cf-_?w4>m#a%|yJ!<4!EHxT zad*D>ypF4L+M5dde2_`O;{$m?ZvfUkInmr@y<23dp_taUZ7+CgbI8SL$WmS4xZlLN zON_PRkv9sS^yKwIz`p;G@?sH2!0Nk~>x-GsU|%yZeqJE%0r$nMH`>rM^G}%S_PydV zFZR(um+vhiXa`2@eMDwf-*grEDwDpCkfLBTc|wh)3_G|Ft@wHX63eYN^^TqH*MsmD zd9}4TJYL=6wi@0n#op3$J@3=7dvGz+X4>%R!X)R=T$3noj6q4T&pOvuMVer}6fKnT z7P{{<<7GxHGkX3r<BijMfvRs+(0tR3>L?`Z>l4|^s{u6YQA4nIQH1-=PQGMPX#nK> zb8y$zf}y+rwK!*`sFLir{33&jrwPS1h2JMhmY({O4>r;Q{vaWQG2^qv!;K~g1DeY2 z5R|7DDy-*2_>v?L)l`B_w7j6d>1>{F&LkIWI5G2S?#ckR&Q@~XUs}2?!#zFK@6nTw z!^*XGGG<&7gT{5Xf(kD`)()uLPqMn(??OmVI4tscwa(YwqGDm|)0{*Tp0yz<W;W{s z8pu1a+D_Kx_yR(Lv+HgL)h={k@wBMu%T@!6d>Mmi?B{Ku9Ws1;gZZQ*dd%;n=*eDe zEBciO;7gd}H!?Lj<?vgamk8~-<-^ptJAQ_+J|idYd35a=fb^Rm{Xf}EU6so$qBy9a z{sXFFY}R^ZRfwg3pV4;ZwX80nB^Cp-YYc*Rx>Oqgt#;Y@x~6Fz$cO_-+r3CS2qjf- zQ|0RnD24OWO-3hUFequ0)s4M7?_7$heAn0SRPA@)w`dx59={-Vb7|+HSQVsKR7wn0 zOvG?f)^%fN!dcf++vZ`FefmE2;}SUcU*C7C8xdm^oz2)~e1J3qGnHMu;0YxqSQc(* zl`{hXN}~g?hZ`q?O~Z2IDL)u4_5EOoHOpXq=bB>Sb*peh5d}}(c|zhypsROPJiX<9 z^{I`Eh{2#oYN?59;FihbgK)u7C?W$<Yi*&g=Bp*-oUHrR*A7!<xq$5<Ej3CbOVTEs z2dnY>I5zw1mq|(8B5tuSARs(GDh8JwVtH)5r$`bbj{qI7;|2JTucO}wmj>I%@iA*4 zn>sFK3LydLV+&+`A0h3c2pB&-sZ^rn(Rb4$+p)^SJ3&7fS{E5K%T5K+XD19VcEBQX zCqm7%yh}*#b&jg|r@zOAYRAocc())k08xqRI7a-tS9rp$r4=>fi3KHbGY`5RY@ATQ zX{@RbLQq@bI6^H*C?qb!&(qVxA&MX<mnW1bgbGKwFhFJ5n=Fjurpv5NYcA2&RO&50 zkvOLfp?615Gf|1YPHFCZ(LF>TSt*{wK-E-)K<o<#&27V2CZa=XzxTun4<;rXn(f}8 z$LnZz;h~@iYy7Hb6xGL>EA?2dR_7i|=7~b&#;oNiEPLEGVrY>fjPsvZ?Nwcb%aX36 zo(yQ+Yqd<~Z0sscTKSMPUNQ@ahny|#5d3N1U#^&JE=YS3=mC^Fo@Dw_48d%|6J+)q z$@kOSR=?zavdGs|wqTovo=5qZao}ojyq+g_%RihIG<RQ>+k!U^?Hqk*bR#}-^vQp7 zf69LT{OzUXm*(b#&uM-!3JD@svM!}evpu{oHLE_eF^6lSsVXNWP)D3sc#pgsFeHPa zm!I?JB=8=r%KA0v>A@P4<f5+|)UV+;co;u_)WZe&_V33cVD(erkh)g?>Hzg@5f8!4 z`bsECK5fH)Zh5dfvI9r8aY$Piz~BedYT=bmmAT0kii4W!Bk_=Q-|0{7<lNutVoFo= zivSYU70xY!8LSTlQ_KkZ7HV30BD~{f#{=$DPrzUcGYdm&TQd%IHD<qxZa{|~A8$nN z@!8bY)&YarVuC97)07hFXYZk=H^PycR8<)2xwQNt-E?oBR%1u6iLn~CfnTOp_3p}B zd>rLoi>82eG^4Xlc%OcKt6}WOlt2oo9pqHW6rHFZ5k^&4oYmbfVM$4!$OrqPo$|2S z$|Q&E@kCK^4i)Di>0rl@Pdh_*?C-a=mCMb->1iq%!LR460@m}SUTv;IbicOQwVXWA z3>xAwG_{mV{=tBk=Is3H>efM!%6qZu0#;5E_zE`4D+-PA`@vvICmsJ%)w=<3#B(~m zq-L9@aHyb$F%K{d>Fx$cn2ec%%$3=MVhppL?8P8hU3@77t2K4FEW>i#-HuYVX})%L zbK2h}%}@m@I#^vmk|LYe_X=<LJHYJ%(r0^>OpcEu>6w;`*}4@qrcf#SKKDL?q?whT zrfb$rW)kGe`x%Ww&77=<D?KSAvSO!oN8Liv#CbqAFpT~<5BRN=E5$n%_-z@wt?d`} zdE$_ltXi$@FU$IJfY~~R$s88figO<2n8eh@%<6N5)>T}<yVDY5+(h)+g+pOJewN$0 zhy33&mtqs<p1^9AY-ICj3fBXUR8m~?`G$n*G!uISEd~Z#*B4NBL2Ubp0hx4Y&e4M_ zgAqUHedh+UJN5cA^2!p3(zs&3i6IY54JR5zPfxgbG-@iVBf9I=dRJz$?wQ;NWz**j z?$0KpyUVkk5G&5gq8PW@!-Mb$-QcI%+UJE%FrshooxGWHbdO0X*#2)r#H{c2e;*?L z4bU24cdY#nLa{91c@F(h{gi~(-vJGPv|>N9NXqD@{|wjtKWZFq13;48iJxLX;c0aG zBBQAMs9M-}$RyoXt5hZfY7<quiGa_Wc_n8z^j8$VtWXhs1n9vMb*FDmg)_sdRTvYY z+Z*WhMMgfR!B;bphE2HU72RiO;@k;-RMJmkhHAx6UPfX&s0><82Ml3f=osbYcHDk2 zkby_8o#ZK^+ZsPCt6$-KP4Iu$eTN;JqeJaz*8ZqZ9W?cs$G&y+TCSHaASI*@r%A$0 z^R`%;wx9$r1(2Ub_5F!Ht_wtm1fwnJG$6hd!j!r_!bN{GWfzIjB?z>WP#mSuA`T9& zMh=!xDjzFM+WC9F6)bzklJ2}+Jialw(4CKam^}*K7uDduRD<7__AoQivY%!OwaF&~ z-^+=mqo%Y&!C3ncdGZTiCnj_cMs!<VDXA=MYx3%^`~2d(Hm&7K`Q|W9EvY{BQU|=l zwv131g#!8R^6Yz7%~mKX^!U2w0qXpVJxP>(X!GxHo_kC+Mn>gS`hpMBUmq5(J^CWP zGEPN_xoKWIE-Pzpjr_sjH7%Ho-5MU-<pr=k<VaBTffC5EGefXT5Xr@+=`u&ylfSd4 zw-x&5q0b?VO-q}m-j1+kFtx&Z19z<}7x4#ES9q$IA-GahNg<5SChV!QaGnciQK5>g zlFRT-wnpV_VT)_&mXz%tex<oBSV4nL(83_q67)^a!dT4){`<!?aS~rU$(PCj^n`Tr z{l;cPjR3);=I5#TgNAC|^h1_dVTYp?JME*)jIytkv$L{)JGHxiU5GI1yXA4+0qW#K zh6k)j)_<S9)}Ge?e%-A4#eZJ6ZsWm7?Z2;^wJS{ZIe+`$`MLUMZJhJt3;g{C9={FH zIIVU`z0sv$=#FGiu4PD$!+bxH8TB1S4*g1$44ni1BnM*?Kz02GE!wG{lP4Gy@x_k% zYn8hcV7)<}3w-A{PF@i**+Mq3f!VxkSG@<y>(IBQH~ebgi!3vKpzd_gQl7vYz${`} zao@|*tcVS)aS2GlflBUYzn-3Q-ImD++Md!KxMyW0zT@2ET9;Qx1p71=YR8mWLi}gD zl+-&S3{<V-StE&6*EALPurd`WAA5AK`jnP4%)^8VVi{WkOnQI2%GM<N^(Ws5NLvO{ zzdC`KD8L=y=L2w$`PZEKPs<M7+gIfPI~F;hOcXkCB4s8`gHb>%@YbECWp&dJ8k3GY z7x7J4v!T&+(KefHDM>+D)f8E1o}>($+C_9XosmygJ>ma4iUr22Xs_|D7DhRvWb~@+ z>2y?v5}D7|>*A}a<%9_u0cKn|;P~t7__AD&f|aZ}p}cza<K!(6Ev;C~@VTqwt(5oe z(ReB;6UOsPv2T|C_mb;t8@k^zq+81v4~?Ig1!Q|Q7>^b!P(Z(RdJHHGNi5>>pfbE7 zBiYLe9aCe?nHNfRh1tBl?oy&jnh8p*LU7c9_M$np-sWN<ZoG|j-ueO=NJ76ZJ8QIC z6dLqRZjCupxoM2G6+?k3?k&ZuqKq~3w|J{&jq~uq2N%Rk+^W|6KDDgf89!{~V)g-X zu;HSQWIzz)@VJA<EqH_eLTgCX(z0ChJAQsXUlP|0c;N)8D0a`j7)q!XhvRTks0l3# zvpU77T)c<!(Am0#MqVK$2@KfM&RPB$f?2RNlEq=4g}<;Whl2e${BekwOBHys8i)yQ z&umo-jg<8e6j$=Xgp?|WU6_B(T{D^3200S8iTo-pexOfi&>R@-+mo(ME+{GSx7X$= z-Qpu%^pgCziLDXF6c1mq1h<8tOqb8&ZG?Z<d^O3vqZ%noHSN{3L{flw_Z;s7%^MqO z7B!VCYSTyuN2OjRw~re;Vk*DKDhhh3<?d+kD;|f|e_?{rIc<o)dW5E72qq<|mtU3^ z#6NKQdVg|Qy~HYyZ1WYYV<$oT4kgl_S+27d1?nc_34ugJ3~R>0W@<D_K}nY^X`cB8 zdNZ02wlZg_Z9+`VInBmR>7+@Y-cO}lC5*}XhYs$gy!m9UcJO9JZJzS_v3;5A$5j)! z(f1A2D{YX5wy}pFI6ippMo!q%^T)3(OCt+8<OWChU01&5<9^4D%)%gW-1hXqY-5Tq zO{w3+^sasZ##CLRjXIBts58ipnXs=P*tg$-ZfhAMs=CdrI`>klP^$Nd@5?MriYC0@ zy$^dsvkxu_96%}=ZrvWAa5dy;!q2d<yGs;b%hwG(b8YOogLBMvJSZxf*Ng{~TtDm` z`ysN?)@89eWv+WJpR&9}X#BLJ`jK4kD)fEZzT{P#>R#RcAC!wI6Bk0LdpPTCWC}s= zNj7r22~+R16Z_jD<wi}$T)S3Kc^y7iT*uXIHi@Sxrt}8pZ0&T{SUrzc*Ucvb4L0_l zhmg_sxkLE|OT2!Rn5P-E6#ZUyiA?4oh@?^0hza;{wjj;7Xz1IE4_=KHt`BKmnVK01 zZ8Ko+9j);-xn55c(ZQsfgsw+7XO<OTEUKJNt~XzI&f6`jT4j><b@yP-K$Lci=J)VE zDMI4;YICYxALB@p#S5AQ_C~qzXPkRCn;#;Fa(*xb`?GFYMwE>%EiNBss@)$pH%Pu? zR<CsB5h74I0beC48RAydXZ20K!N$>knUL^d-QM>TkE#y}zA@=lt?N!hkU!(H!LI(& zhU2jp0CVc$o7{6)G4N80nY*mG|Hb%er6;xEYB)uE5V3quWy=(Ss}wGL9h(tt&Gf}{ zA%%P?n(eWkZcK&bcO~w7H+I<g1XpyYf>k8_LnD9tq*Fo<Fx1)dSIsCTQ|zw~E~`$d zl&O%8N}^Gn7_0o0R5qKKtjxxm0GVHdDyJdFa3@hRtxVNlw11uP<F>R<yTN_}Wfhu7 zu$Q`DYP1EnXc<@k0x&bTtf%qQAKEEiWCxT>Ue!VQ9DlQTW*t{RcBI?*Mu6toDkI;( zQkBCC4jdt4!P_Z;Veq|XbBd@;+K%RU12VtY1odY7s0_Br=bD+4dR*%D3o~mVZQ^7( z#)3bi2%w#N^#>d|7p(d1#D*4~!WQA~O3WuE+)t0lBcxv{WyiNs*OokXn7JIO8ED5f zv6*kiAyd+K2>#M`U`YBBZt8S3Cn=_jTL>5Mzz>Fc4;A`OVB=!Z*346xiuB5q5et|> zMKx!#${(51Yot)M$Mf5m(0T7pE=?~Q*141B%AZ*o=PaLciRx|LQ14y>a-Mo|QRGSu zBG7KQ)35Hn)aF;NTkcYid)?KAC!4i;wRL^+goK31W<=fJiLn0q@|t7Qi^7{?O?~KW zd93sf4(}IQO}FQ_*WsvxE^tx6D@atGUKiDJaj|1-T?z86luxnDS+U*(DlLqCm62&S zat-e_Jo+_+6nLd5zVhy(z^tQQ6@a+kh#qK{Y_dUzPpEoML#(}LJ;!-=Cbo{uCqQT3 z)SB&4jbZNwTBYXw)bkONzs2hYWXl26)YL9|VqYC{&hYO2O>FkwaBVfs1BLP${T|R5 zXPlUWmg>gX%}B-V;LeUHq(nD9EK^|RuYZWE9`Jw$p4kz5T0bD2ma)ika_Yh$$jW5s zv6T=AG1KV#X`<3fW_fk$<(O%o-lCH63f(>=_Ve(dz4>LY_UFAVCl1y!fndqwM8Jrp zd)aaB*_0AsWql;b1vnB8p_(a*Zy0!p|J(%K<OOWJuOa(`L6(ThfIVb@=KS+1&5$Wz z|IHA`V@Dgnber}t99r|wEgbfmfZZ2%n-7~v$L>`vCkPt<cm_GQ+x&w8l@FZS_x<6$ zqJdw|lpOr)x&J)%dWW(O_^9XsXFI!9wqabtaE9>bUjNx5|G#Zf#Qop5DE#MzWg3M@ z2R@-H5_yAb-H=SOHmC?5WzOL{A|E4^mr`$Dy1RoHcAB$<$oRzTJ>>9?IlDLTMUFQi z;fpxL6xK&V`g=cJC9PCw1;pTO6}}5Q-%mF)jo6n#5X@?=#CRHQiYC4D+s|IvU)EpZ zud7{<ilDpWN~kZXHRK-*<Rv1Tv})4h407kFVHXwh-8lVm8;S=QVLsT_>l81vM&ZUM zr9jUCrh?@J^efqF?AqgGCZEBst&oH?M_uR(2)@juvk}Zus^tqxNCW(o9{X#g_w9#G zJg&O8IQHCcP;uYN;vG}Sq)B`{!2GQZ7dZR3dGCLheLvt9i8^%x^?U8senOxVJ;1BY zy|h8)+q>4V{1NrK=0%LUY=%#t&j2S4&!lFfm=R#RIAzqXXEMrvFJ{vT%=-}+Q`^H{ zJ8pkKYo<24f?NiWjjOU09rC8YLU38uUb9@@#<tzc{OanQS6kpV$_q&9h{yFEIIY&y z<;Vr7NGat6@JFJu_8O#Pt5){1J1N~52Un+CT{}-%BtP34K#$nkS=nrC9pKFSUykm; zE2=AgFsQS;eI-J)+eucO<c=OI<&uTjUa=5wM40k1!Q|rc7(6E)E{_@W9^S{S3)k`8 zIiv*xu9YdLn^=9+dsZ4gQ1TuJ(+?m44ddy%%xzBwXHZinJSeMd+UP|89%)B#OROGj zuhu?3b!*48+Qxt7z=WEepf=T<x;MXS<bSd=*eEQQ-ur_=96)2g_!XTSov?75R*F`F zKvl|a_VKn>4IMesB=hw6hX%a#z##Hb#oNx=Kw^KndUS1wkteoE%4tjY<6-|{?_s~5 zv0$A8m+O3(*QjNy22v(yZ)4QwP@!EBF~by%zOo18VCbU__ZY8oS9#bAs~czXtEND= z^N&Fj%Llhf$I=bm=x2M3*H28N-|R8U{Q{>zzyEQhU7vq^NAIXEYZd){k5S?T63w>{ zJn*1(_krV4&_Vl*@-9c`#s&B{;Y)yEd@W?y*AxP^)e$9mxYLqM`365e(y_QCD|d5Y zx344iV>aF4F}SB`=-bT}vF3ra#@CZgvXUejDs9Rr#m3EVTmiIW5Cq^w7h;MU?Vrzj z72vnPKm&0%;r2~5+X3*BgJ`Xv4+ymX0idmtaT}dv^PeRka=-9KO)Q7aElcS%89HDd zTSd{U6kiqScqYqT8%}lhwcDOQ-?!Q;_nmP2gM%q0h6Wc3h`hJX(u5FeF9Ts_pum0d z0)Z+eITUm4>&mF9NutgQ8!iS{@*%0z$+xR5?_rEe^yb=WJ15V^(it=B0>x#UrZH5} zkTt)M#em9O%YB}CghBlMr0&n9Nj*jz&H+Ul7XoWMKr&bdj-ad~Q@^k`wT6C#0V)m3 zloha4jIv9PU$gvbBy4siVwz_VqM8aoBAMZIS+{9<%Ws_zhe74T%lhU`I|QkPetc2I z=fy8+29d2a`D=|apc1-VG}Uh28D)2dbNnJ(H7~pYgGjM7TVR5<gDn@L%1G#gI1dY3 zQ0F(1gkrr8LdRFXexL6%=Vnbgz_g1@3IQp7htjafk42f)H1)B5D_ctU$KrHW41@y5 z(xJ|?+ViQ0#5u$)<b3J^LfWxni0=M@9`?>&R!%Xa+3=83H^X-e$pHxQ#0Et|M!J;Q z4zorU<94$JxC0U$SKtGmU}?=0x*qM;d|ig>9-ixC4L!q`*>p~e3-hfGq*Nld6MV>K z3$YkW@X70coC6f5CoqNq8mWKIUNW=*w~4=&ZvTVgO!*yj@LzW!RA46KiC@m0oP)xw zi>jQ7!;RLVc~!Qw?Y5;1a`LUd)m@2?jdEBEeGdv(nZ%MLz-p<U%q>Ldsl5cVq?VH{ zt|eIQ$S_N{V+RlZ%}_@D#Zb;GCIA&6zcPTj&#MCgTuKmI0!sjodCcs~;L0ZCp(-!x z`E6l@-*bQz_P?8;@O=Lsk>jOGNcVYvzWVUe(zaxr9W4%#<DDp-yVWXITYx7N<ff%d z6x^kX_y96-S9Mk++W`_U%!bC5<~xtoNDSR+57EG-zqg#aUKbax>wf2O;LBudAXUxX zVlneHs?9L8jV_Ie$S74(F2#nF#7B$S;EKB`m{?5Y$1j-?Pz~;}0+LD8kP;$Y$~8@? zS3dLd;Fg)NK&OyjkX7F7<PgCEKaV9(7C2(W3-?in<{yrudSSDmqvvjAGd4L(_cq2d z4L}kO<NAmvnBEV5q0xCyF6O%8yqgkrgK|+eqrn~18mN^?Ia<$1*HnAU1|NKhDWYV$ z-GyX4N)Pq_g26NuQ;dX*(q1GY@`A+WUvBb=dY2K@$NIAFE=z}wS=Pn&xWhaQ79EzX zCHPOBTcj?;wU|E2H;ajiith>vYLX5sphq4{<pOasc}l7e!jf$Lw~O76JJ~9#NGNPD zzRSPnNt{ScsCUhFmgz6cHCf7UFT?zXa(Sq$#fE*xdo{|~(=WeewAp+ZeVD&T_z=-$ zU$gAJR>z-#!FM_(h!0PE{hgy*-YNakwLQ6bU-Hz;xrHozBbbfV4Cgn8{rn6NMJh^L zs5=*xS5du?mFynZL{j^@nOC6S@hH;$L)oYKd{MD|YRwl_#}Fg1cg-FuFe!_$icY3t znf2fBJ@1u_V)+^l@$r_3hst%baz$of4)u8dWveHxE!i0}Zv7kg?$g~3O;wbwtjug@ zQv206)Y*7@w)~<77gMcB@-Hm1tF2atGV*f@Ht54eVfT;bD?C`OBkKL7O;IV|W&5Ty zf^Sl~>W)#GMy_x>rx3w`l)ctjmb7J7wtaJt%gJ)H0@<(qdGjsxs{Kmw_o?_MwUKv6 zJersWza#Me*<C#K2Sc=Fb|QPp$jo8iL2s5?gehemqH5jBQ^uOViNwS-zp!n4zoNLX zG@)1O)Hva_AN&XP9lun@2+Em0Dm*c!ytEj4y$Bo+UQ=&Qu~_5}JMbtzZolg)ebldH z99Xg6u0QgbzN*Az`LQ7KTI_X-Nx)4Ggb>*#Dt&Rd`1PUOx~c#4vhh@PS3ay%DYntS zkE)<RLFT2VCd4OQm(wnR?6mbnWsP<{cuQo0ahc^E*@O^X1DkBOQP~dZPl&-rpD6|z zP|{49iHTG)_tUsX81>7^`XvVfbWG*bUIM%Q4~FDPRLy&*F5{ZI)p_pyDKF`r{Y(&O z%53V24<6f9|8dkzBw7B7(S{VyxUA7nE3gOKe2mf)<EBsu^a3#Y0&b-qdB|Y9u0+*m zRnWX0NmYqw%ifAxKNz?LYK}pJD4GD&%_6>))F4)!3d!+(x1d6th4I=9!HES8vr-q} zvM%KOEKn9UvaY8MZNYf*I#7vPu+AsGe8RQm*@tRLLV3JAj>^6mX`r|hXemLzJo94u zWiS8t!cfQi)AW<z#t`a~1-|gQ$ZYpDr(55;cWb_!2zNCIN&;?wd)ZBz>!bzNWA-DX z$jwjxZ*~AE`NfeZCgmRZvBgb#COrP!d+YlxDZ%ZD4uvt7XU{4gGynk??daZ_vNYGA zuT<o_B1IL-y+!vb|Fd;m=ko0E<5FxKalRI<e<+HJ?`g${eVK~(IQt9D74A7C9Z&f5 zgia~;wA^T-&&T6(b+tzx`2<(l;aj7@TvC|63Drc@l0f+Pz|yQQ`^~ZT9t~1>0$v8Z zqhhPF_=5rDtdLT^{(f>4`gqQhO`T`z4hJ~Su81Z2%Sl%l?+=Dbn=;omaIwyr!d#o6 zWz~yQ6iJ@B&QnJ@^1*wekG*E+;K5H`_S{w&4y{R5TJB`>8;8aeVCF5Lm1g#64Rqb* zVZ>b&l&^UD<1Td}hm16aeEz}k?D+al_v0(Y+xgMZ>?;M(Qjj0KLh-;C8}C4c8f-<d z=E^Es&}x_9m>1t+_M3qh4r1R=m>d(^MoZxe5EaA7kL{1E$+?*GqWfIg^(6gE8DcZp zS@@WyG;ddpUuxkyc+aoA#v861W+UTJ!V&7{{G@VB3X)PmcV3-cn_FJG&8+$EYzk)J zeHylQ)qyZ92&!wVOWnMo&m#4@Fz^54?!BX$+_t_^FxV*~P5PE9K|p#FP-)T)Nr-fj z9tgb%h_QhJ0#YMgYDhwnUV};}G%2AMDWMmoOL5;v_c`C)=iKj}``&TiKfdpd!5A4C zBP7o(Yp%KGn)CMy)t2oS4M<(o<0s=V#o|B_mIy~ckAVSdlfAV+sV-TmeGgPWJa+yy zykayz^CD>1ZR;n&TIQoHGfTYc!qJO`C#Z393#5DOM|0{i5seX7<V<&^NQQts7%054 zFL;hDcMLNM;OJS$UAr%xXXEI1tB&=A4%ht|<xwim`GcNQgtdELNNt1t0dq))s)k4S zE3vnJTIs8VziX3HW4|Zk7_#6g=sU;c>C4)_Jjop}p=7!#E9;32<0yP4P^8s+{bujO zg8W5(#TW!#47y|1VLff8DU6eW$ISoH?!aD^ft3JJ%7q-;l)p3+Q=+Fg+7;917ZoR^ zwRpF1H6gG@GQRbbC)@m|tO?6u!ZdgIWL?zAN?m*}p$K(dV$BX8XtUiUAf+upAGob} zu&Av_s&+%Dp<fmY4W-U(d*n_9H&5tJHVp^FIISW@(37+>ZVBNXf;;mu+xmsx<70~L zucNHBRo@EqFSu4Q)|oc*R7}y+_&aL3nY%a;@^L?s>&J`WJ9~%tmq`x@IY~Y6ospsK zwyq)*{uncSp+0tgYx+bwzdE<hDXo8Iwkg}+ZIiphu3=mR?}QY>>GvXx5Za;>Ln`fU z?f5}7lCO*_EEP)}p3*JUah~LCSoCOWwp?_b4vIAu%6wMVx}=QQ?qK$n*_K?T_k2A; zZe{AXde?r;7oc1?q3kBs7Pt@cF5(O+Cd{SFS%7VP^q)Djp}>UsSe?8;Z@%_L;jDdO zal{`SeV-gu107EI=5{Bo0i32!==*f%$qlml%B1hYUb9Tj2e0a!s-`xL>L;#=EZMIE zlyo|H8<Yl|5#PdJwxn3CKNAphY5K;mORv9J5>BCdF;{DWV*nZxd=?~d$(aXev1>h> z)1bt#lJ3?KhABA<l0a`*8dS#l>-Oa8z&Fop22HC2j5%@(IZtm;X^G7CuH2teE9bub zC)KMS`#6$Y&&v~-U)lZjjM|2U$3r9HtZFK2%I<R8c>kucy~o{)l*I*LReQd{J~|T` zz{J92w|^S3ny0oF^NyF)QY>apY3>Oboh&QNYnWIYEE(fnC`N0SxFwM3v<CPGvQuBb zb!68-;Qg~>r1W_-lvbSVuz9nUkFt^^`PsAKg@LwLV0xi!*n$yy=K6QaI!%Vf*omNq zyrN+x{4=(RmE>A2<NkX99g=-i8rQDr4gBjxmG%h=BijXPY=eo4n6}sKG*U)?yNhI2 z$nR9qU;_3FLvt4SdJ^Ik4PC_p7jYlZirTN-XVg4Do&z!&2bE!t21D4~S+BLDh{KZO zj-Z5x;hhU;=_&Ns7R*TArFT)%Yq5$#p;*62-EDlpn;Hb!gXwHZqQ^f;GsMfgp_Db} zXZ=R*yC7HA$87eL?PYe6<m9_^xXAoPg4xivxY7)okxp=6_?#S1$7gq@ZNInUBN!Kh zS*MXAlm3Be@Q{6@f6d_TK$dXemhGo$B!jSM{((ZRN8-t%Pp8*MxgXniYSG;30%0eZ zlmOD=Zh^NS?ps~0FUMR*nfuTLzmqtd!(>7p{)@l!h6hsP1OE`f=7`%2bACLd$oP1f z7z9VJG9lW|o!t)HbuAztb8qxA`X~F#)mgirrVT*DHSJYUuzJF5_~E>!HB0me+FId% z+!s5Dd<>9B?Yq&-?4K_;FIg}uvrc=08?^fB7hwu+$P6$^>0yfNOMBCc+=vl2B+BVM zl&jih8eJDG|G9Bgwk?K=nq2(pf?oLP-H~Bbx`-C?p6uFu1DXO*TVB&pbq5Y{X(1z( zo4e!eL|px9PzW`-b@05-%^6}g7p0ZEa{z-?w=s~LNE5+>t5^ER!Z8Mu4i)MPD!D#B zUP@{2BA04nm=YxC5@g26fjc7oKAZ-F;Xr?b0gw_$A`@8blJCWtez&Bf>-dAQMwUk7 z7J!uY2gX48G`H#ewp_YwOh*Qdk*S<!B7I*POE)Qq8T58eo4>XK@`N8pYmm;Ij?Y|) zm<nGL4(Pr3$6fMU(E!B91TX3zi|#RSk@u#IN}^>ik6x{@oR^n|hqsk@EEGR$!YHZI z@Sy3JX@8Lr{59ZsS>qH9&;kOy*Wnf|tW5syQc!MEVUk{ZUL;Rr(?kV7Y`XzY`*Mm7 zvK4@IykFV4-Xw=hia#81DBAT?I@HF?=`2|*FXR;1deww|2SOuISQ<+jXR<nYG4q^K zyo(|PRhr+=dof8=VM~EpDGSD0N3Cne!!xTjGKw0jm)1m6`aG)LZl+0GKDKnq?<dzz zFip9$e8}Q2s^!@?veZO;Ew+=AaJc|p><da5S)s0^L1uiE_j~f$VCk4AWxI1arB3ZZ z-p&&SgCAcxjkOAnFlv`hvGM(;2mbH-9Gbek8Cjm{v{7%cfIc<e6Sj`cJKow~+~|tg z*8Wtv<7*fmztag+44<NGTBEdmky*!Dc~x&vL$NZ6+0RZ^f)IaF+2}_ednfWpRq61X z-2POwy;Y39&J6s|B9&Zr&omz%daIPr2jO^rpJ4=i+0B8#68&(d+ZqJN9du8k!~F!M ztJ$XP1O54+P<!;|b5&{ihl<kz^%pmllq~q6-8f)}@1z>PEbI6;jZikbCo?`k1J%yv z9e?|UsQTo1a`fo5eD=3M_g>z=sRuw61?r&N{*G(RQ_-K7Ki=f|+Xtz}V$jLhzkSvB zzkl*FO6Tdp^kld};mjNqYFwPUg0j~?5y?Z9;IHovb1W1vR?68c)?C(%34XJS=R2sW zE}V6TR85=^ziOLT&PcrP*j1U08rHdRK-_@Cz4&|^Z4fm|8BoV+yJ3t$*NfK;;0gni zD!9v`f{B=lL#5W<s7!&8#-gstU3{q_u_tLV@X0ux*Ey+;#`TF5ozi4Z*Toot`~i~- zqP}{45Xg2LlQBZ*Cd)A?y+!3~I~6U6=It4(+rTo9#$dki0?-)3eX5w97pH`%BN7=v ze5b~4V$@y&*cS|jF4oZdL-V)z&i$eb{&jo&YrHAC14uRtq5#;Rzz&{L^)czJni`{r z&tANp^LgXK&7VH#0)h7c6x!)!h0MS@P0q_CMSLx4<lztUF)5s|EUriGJsb2%yg=0C ztsx^!&6X{-wS{$GwYK6#=kIT|X4SkyY<mlfv!Xp~6grMw=d97(%{<3b6=Jo8_5+n6 z`#g=W8zOLgUJG^r!a31eDCmd9p!IY2s@P%RtN^3S$B(Y=x~pv7nd_>VT<%83OslsQ zmp-wzrg6zqZ`!mzox<O=F}C#1b&uWMzccFNhkF)oRS1DzTwbiB-`kvE{wR6d3xjTO zrSpnC(sCJJUG&y52A4qmx&b3y{1DnC5<OR*KIOc60_OvDe&&`>K&$y{0NEoHczJru z?zP<+fZT-U))gwC<&p-#>JU)><Ho<foKnMBGk{pp^tbqc1Cj_iT&VYg0rVb4^Bcm3 z-YJE_p&-PZ)R5aDP`2HZz6--Xv(s4aPQ7|WyDT9k3NLckM5f;8S+U!8Y+h6$P7u9b z%}YJZ>`98U&1sZnQ98puel>>X!H?wdUe>IEeHAsCo>1j9u0i)097eKK@_vzqHFJ*< zq9ldi`gZBKX0Bi9uup!C@p4_zwOM>xq+M(Ui)GimyN0#ir|(Lbj!J57X<qgJGBkwQ zzVx{>AOTXGuZ1w?HYXtE*{lBu2inG0Qf`W<z}3k>)69tBb~@{=7{VVWKo9`WgSP*} z`VVEF)J;boSS(g4%&*4ZsJa{^S+I{EcB))9a9tOLYMT6*Pp}j<b`VvB)I>rM#a6i~ z;c>Y-J*ff7>jT$?dbP_cJ~m)c0haf$Lfdkw@qX@NVpCS*kCMJbvh2Uu#Et@U>^%j? zbN&UGl0gvHb;*LFB5_XmQthMV#Plaxt6}A0{MsRk&dG1wQd3B81nDho>9&1)f*x^y zeuufl4C3dO-JD`*$gt!6lS-*<J3<~&<%6s?uJX3J*%7?Zgt^BIVy6ju>a+Iy>rYDx zGnorx?y-V+6&(=-<1{woRK?tx)L^V+?#P(c%BAw!YM8U!K>jR{pw4U+PI+LOyXIun zrx<}>0PcQ(Ux@sQ`GXAvrv8Ds!~)`^QJ%N4!niVl_!NeVA88#1zqLMI>Fv2bX*Jo0 zpV6J{ezYyPpLEDZ>+ut7%b9Xw#k$KG@FG`H_SMU6?ke+?5p6?E{$&t1H4`SX55%XG z=Nnid((fzMlx)S`Hs;M?8JWA0dC{B@q7{cyiW1imNaqDlF4}*WzSezd|D`*2^z>iq zdvyJ`d@hn}|DwLRf2uF=U)6VVlm38Kwt1?<MP;qT6cs3Kaop97BE)7n2T<HyhouuZ zcVsn}h=Ix9H_uHQm0tXlD$VpqOA2AUR@Oqqyr!7Pu3;O4VgYf0_Go`#dcT0UPrbup zBv?Rvm`)ZDo#?>3{xy_@?>V!{39w9x^Ps1fj=;AcCY#C<VaezE82(R1$@~2l{{|0f zWp<{*Dgqmo<h(eKI!L+GFY^eRh{<nV?x@diyhmxhF)eSgIq+#SCbT+bR1D)m2Jzmv z3jR92ya(jx+XOX*3f~}qEkcN>zwBHWX3AAY{L0LUVXM6SVZGRj-1b>rFBgXM3(9U) z^3~BP52|{dOUzNea2`)tZULDsz2T)OTSoi_F%a*F3p>D|$RIvNIigwoQf>T9w*>9n zx?-ewYybYekV0}}woYZ5Hk&}K5krF8cIiP<Y9#Z&c=BJacKzQ>G5t4FO#IE00aN_A z@()j5{hOctPdynFbZ!~r$_(N@{m@NlPY>hH1QI|S<|=sWH7Oh!+Uk{*69=!l_<Q8{ zqp*;c4uQBPih2E7gOEMHhVWPU$GACD?q=4<jF*u;ANRYb2X=f}*X2=Fy^!%<Jxy8$ z41SyVMvG9pyMjy4Q%NAVG$Dy>M%+>Zz3t=Q<wA0tzG20rA&~=d0g#uOoo{R!+-mk7 z@uKzrnHiq_E?BD6`B;V$8?EbhY8h9!>t%Ga=y9Qr*}_)#lTh5;Fz%`CSN<R}{_XcO zWcTRnyTFAjC9$a3Eq~A>*KMIbAoi#`G^B~+4A%aywZh|l))27U#YgM46H^(=kHc{W zr`p5%EK`Li)E?yP)Fc;}s2D`Nl6}m8$y;7=Wfk=et?0qwvU0-Dl6r`Q<Uvh;E8y}l zFs)sXf(yc5S(+AS%*PFE9(E@Om^xlG21|QJkc^7MCa=Z_0+Y+Yw04|6T$ox7bIXy2 z70vZ(Yaoz?Ey9JAwfHAhX)?O)&ChgoCdUwAe12foepcQ2DWqTJ*K7!KeXeL?jLy^f zc@%z=yQRG7x%S<mif${omrrk&Hy6a#6Rj1Gn(G>j3s$l@NcCrTt};s?UNLJ*YISZ% zyK!t#wO!SYkN<N0bsEcPV{p^ng_kKw_;W3`Bh}uBPcs;CUJwu3bRaEkQr0Mn@*}Qh zO>0{?oAu4aDoUl%YNK`1YItB!O>JR-M^Uc6Db;4(b-c4lZpP`tHCFQw%XHm37qj#@ zf^z~`WQ=T>Pq=>F4Z5#O{qD@e4R(1G?)4G6^R={TrM-_!KOIt#6i#yvKez$VwcG{{ zZn_2_tWaHj`XDwgz=8=K2_4b=Q4KUOL^io&HS=(;N<z=Aj)`xT0$YO=9Zd36^14>m zjhy;8jJFGRPv5!i@?m^M+_$ty<=oZC{<|9uc@+kMTaT9Oh{6olCkl;>My>a)j=Y93 z0v7m%O4p`k2{`goduqnqQ`gboY_}Sxevi=~r#lR^mMThPv~HbdxZ}m;H1BVMmuU9A z8dlaAM~10Dg8D39ZYs{$o#!Y$?GE1GD!Qe1bA1b(D|3-gSkuoV!{g+6rqBYi<o=#G z7x|k4#_}SFS84L<+y6&ExPRioHBo=NxXXo78#_ivZL4BRI6zlaFSUc_U`yS+rg&%B z7OVKO-uxwz3~il9v7y2U=s*qJlG&ROq$LUOOa%BT5LoEi(_aL>{}c(McIz}N+KWfq zcOq)6+pZ91eM0TI@h6o~!2Zm?h;?&eS&n8b(xIKDzTvBdHwm~^UkBjo8cu-)_tVz( zu427^xH4wpi<oY1O7Hyrz*T$<BJWx7p!7vAPHcMcn{4fp{Gz3sFK-PJ$+SN1m~(?L z{^k9X2i*PaR4y66Q1$-}RNn;v)kj5Q)?z`N&bLNodQt}@_0FUY?2W^`6?yruIrBd& z_7Y;Wc%wm@8*&!TAqAHE@}S~9qchN9J%e8sq2{55JtbGRw&tFk{1on|`sCL0I{G|u z`k(Vp>1*GQ%A9Jfc3#HIB`boFtXaYWu7;RfmZx~pM)FPj#|!{nq=@5el11yd7<SX_ z$*@=Gwp(J%)}UmUYgmXEzh@l(6&}OFm9cLvxg6g;V~s@el6P{+Dx_kLga8>0DP`yt z5Y=Y^04B!y6-^3TEvIv}QsZ)Upp=Vc!=n4zACI@1_#OP+qD*F@{MUn>+$_22SQ=>Z zs`@^o4K9cqE7)quvdtR4`lCzqB1d2_%bgU|_1)EmMWdEfctFcx{H}6)t5j?_QW3I) z0FPE~M>}6S*RA+mAu%?FEONiRL`>bB6iZ-AV<LA?zxZ$~ej<>O>KOpidhQq2&(oVH zR`p+YIKsKFS{AIq_E(~;T8G72S>YXBN$%G%Pi9Y@Lmy$crGs<s))yR}(bV5MiU*+R zH>7w6gxX6#|0C~Dlepw`qt9uS{BolH<l11s{U%g_1WyPaZmBI875!hz{uUyzi)js- zusk5sT`KBf+Ti^F6PV~W5#>44(v;kAmKNh-Ov8QZlQQPn6Kb|oAY)86=9HjdiW5qf zn`c!QdncEqWAZ%JWBXl0(zoR=RTpAXoc%@s-h$FnRg65q1)%b(Unb56JqPNyKAAaz z0++XF(Q|h^qXMv3j*n8fl{%~qJ>9uVyngt}yM}7D5C6q;{V&VpziY|s{aW%@=+HoM z1?VenCjTz3;Qp`06>g;~Rfzb4Id*uEPJVIFlHJ%Cye>Xl$DMipq!ahy!4=x<D;P#< z8s<1FXL|0&(HZX}xpuNYj!l?3I{)$3ol#>M+kiMa5}wLC?x`&^^>*AP`>XmH_ap)( zqXPG3Sq!f^tS1+(O$~lqFx-xT0F%K|5mQfJV40z?5~0M*^@WwlhclNJvST&Z`4T2N zIZL`%Z|iYRG}{^b1qb!rRMDo5qeck;b98Y^4|(VnZ8Ej+dj~-fL`=iECz!8SL88>f zn3)`EZt79YpE=am;%>QLkxCUyXHzS41+a!<ygct7zUmbaOW@a%Pr5OIe}7hbs(rj` zpHH|@V>#>L%$6+6W0t@S%t;^(-;Lc<``K3(i@hPR*-*Dp{6_b>5xw1}A`2q~0=-6D zI;Z5RDXVwc8Obx{mOmuUNRP2V(r{3FV4mGREeO1%A!&H&5;>TW#qO^vSOPe|zKWZ1 z5~Wtcl-vZeUT#e9O%O}mEoY8Xpf-$k9JR2xO49Z&-A0`Lh<IRB12{|s1@lH2k{%_R zT5{<lYOXY|EP`b{23H6#sfMk%#WAgEr})i6H6&GsJXJEQQcSC~oW&%HL{=9<%@R9& z2NIY)yrl729en(dz#Su|kM(sYIh9h2?iDU{$^`sE&8;6ZzQDoUn`5Xh0kRRv!cp@L zO5JF><T9x!|D}Up6(cSR5~7yG^ay~2c?G}Td09`}yk8Ny5EOIi#HU2kW0@mjr5DQV zZg}iW#H5IVcupDFV(LUeU^Gw8aZt<~@8Oz!yv<4OtiSrETv4>9vw>)lhzsSpS!ly5 z7J2RK{6l+h{(%IxVvX;rc{}bs2_uBG+P*@WsUkx1vf<%tlir8P#MiC=<Ke8SJ0z$( zRNqO_c<SA2PSlwKv>s`}$5kF36LdGnU#fM@JMha7C4b0iX_Ixb9LD)NkU6y0X*c&o zOcvVHzv@qRF>f-3&{mum95#eO?!0d9EaM+wU+uf8El__7knSU&z^=&UT{|7jW0wgS z7{{6A|JV&C8TH+r_{wG@o;A%rufkGe-^m)zv@WN(I`YP)2F|q<10kf(Lx-J%Z*{}7 zM5_2ghXd{Ll~YG4{OMueFi(X*Z0O0*NDjidO?!BK<Tv%byW>U>rddN+;8qQ8;-Ly* z)?CY-r_2Fp6?8Wrun@+^W+$&>!Pl05*y<Pe@x0Rn9?+<;K82F!2mX$#S|zFx2uN|* zW<GYGqea%o)y0xj|CjY39THTT5}+%dUx?`uUv&6<{T7z1_>Pp798*MYM&iBsUi3G` zkk))OWZmCH8tLKp7+m&wLdm|*$)3N65NLhl-9oW(m2rJO;ycNkn!zk9_LzIZpc`nh zm?-(|C*?)WlVU?@j(NMmu;IJB&<mq$P=sV5pah<!J_sS>v`S*qU>}l5%$w-6PyWuG zhkv%ustMINz{d(@CqQicbE0<;eVZF1^O$D<%IULREt?bUthf56Q1LJ4_~p63onsjg z{ww97eS7KM*Au%->B8rRrXmmf>zKSTZ@?gKIxoFXV6Sgd3cX=@zSl95B0%BjO#o&L z3=+Kj4?|W;vu~=NI9rC*jAPDS199CCGLXh)0kB~PHHn?Fv&sf=(8l%fys+<Z0RwGt zR^z4)?^1F0^n{||S7yPl!N!t81K$-h_u~+4Sw4A~VP^8yXEAkapWiF0HOcFGET8OO zUW*C22F$Z&$6A59^**)FKQ8<){fuqeKgHHq{v|fyZ(>JE&6^5nASJBAW`U=<Iap8o zH(?5udJR?%aYb)c^H|D8@``NDhQ07A7@ljOGwA<!B0}DwoexsK9CZ-p`plqutKoq^ z9wcTK*E*BJ6%_CP7ExX$P&jP2>c3!juGnuE&ZpMXMoG^)r|DsY#X<atIi7Vjrexj# z*~fvxy3WC#2G-Xv0~5LiiV*FyqD<nNu^L)gfNl9>t*D`xXNj-dID0>uAs)IVt{RpN z1UBGZa+OzX%vHjX3;bpqaP3+d7;^Emkv&FS1SELjl|cR3Uk-G)83P9cq~M3<ogc^a zY{mVKOg;?M))u+MGo-Vbu0e<Y?w!C>@*u6w=&*nDX)VYl*Pcr8T=yfCiTT19s!KV- zg<D`Tl3e(xkYE9HRzoz}GjgvitedxftfQ7+$GID&ZFU%dG&@R9$<KZjE7KWc7jq+G zIO|cA&ZE=l^N-lWHeEP@UF7QF^23KS{jzN2^t@|RX$J#A{N~7V7XxJiGN)!h3C}j> z>J5|_iYTz4Y)y*w!}K<Nq(X(&)9i_HhtzsahxCwr);3wDIsE;RD7a?WRIGBJwRZhq z9<QQvu+RV3cGPsF*Szs+3T4R?-k*#>TNn>_(p1m|4;v0bKDRfM7PPV8Umg@}ur!?} zT7ppa%{Fr;S|Vio({?WvrnkrjF-H1>vnM&gvff`CK!E$k2H&tdH=CFlc0G<*KMCQe z)=+fj<=cz7yI~(Wvap-E<+G9HP&kf9b(EVGUou1HTYK91GK+`ft|ArTXu?GzptNdt zKxux_VQFl2W+ZNgT$!P&(9$e+<Ym30)cmXv#xRV7cAoS<bOxyGiH6RKvEWL3ocO_T zbw1o!USs26BHxyKb~=c@Y4VzwQl|m3Hf-Zd177%-)=I7b&aT+UmyJ+q#}@S;K1(KO zK;1OyigwVQa1?Q0PFscPD}D{ooA#ZCSY<j3`Hyu&z#1|VsAs67<B&yEHpTqjgx6v! z)3bz|Q}cHz-q7miAVhQJn<*8CZe9R=qAIW2$VgCIb{yCs2DVz0tg}>pn>oWgK)zFd z4L&>WJ{*Wfm<(FrrXfZfvf;?3ks)+lLcm|T1R??T9jh}Mc5j~N!IC_RSB53>Ptvu> ziZQpBZQ3b*N_@O(1}h4(Zc|V<%i0cq)L$_YerM_5Vx;88fTw(>e*{81OC~XKXs6<T zL?GDGkdQ?Le;>y$?+ZEq=u)Nx0`%V-pnt6tH><Nq3|$p9C)x;AVg4#_-_fmrGcrdo zKeqcxwmqD4G3;TC?MiwaqQ)!Y%TO>=v(WQdj)srO@}ab07=YJlY2l*MX)tJfr+rA) z1ydfwSU=*g@m)^X*01)Jy<fB*%Cd41;+Z!9qs@xKXTI(b)PP&m;C_?1Uejz-loFer zOHH46!2b4>-P1U2XTkp!Gfj(Pt~XdhlCTv#Fsq_-Ha)v@O5VjyM7EH1oQxd1;wxxA zcOSLsl~cT+X)+@jl^;(0dA8i!Y`!TuJbh3G;TywAxzg7@s9|)%+o!f~Bs-Qczm|Fa zwqr6R>gNE4cND~VhG-`xC(MD#2&B0#-NV{@F=Mn+i$J_vqmUD<z8K3&ipl2edglhe z2TIzsLjjo?(@>UApQOhRj_`&HM59#9j1=gq<l(cRzQ3|DPjz`$vcJO~_LY=>DnMzT zkBhh3q2X4@O3n?FsbFxIhq<twdnJo8@StI*R=|`<fCNt|*kC@UQS;Ds(p`{6R5gwL zPGT?*ltAE9PdPBVk|4fI<<jkARZ>?}#1Z@nBTKK57-1<8{|%2mMqFqXUl5&13b-F< z-RpS4o&DQh5Iw-pobMculEGs0y>-Xvu|m>|Qro@JJ(xf#kPMIGMaPidwQ+v<RE|Ze zv1;aa$3^?QeUS_L^5A^xuEg7?nW=j)Z||JGDj9r>xYVk+Ixz@W$V{n3)|CZ8o061! zG&NuV^qoZDDf(21fpK%P*{iA)ylliv_2js3&g|4&anyE#)Twbdki2x1&=4Rmn_1Vi z(mJ>r*JICh6WVAY-|6H!mTn*!JfR0;!w2-o3>tdJtR;|MN46>ti|lv8U5wwy5d^so z>RAaEkSd*_<nSQ3{-a*h?_&pg^q&Ui;FG-p*w)6U_A>h^1I-y50Ga}E$-J;Jq~-Ax z+0<mc@k^n<mg=b4F?>Kf)#tfSqpTD@eAjBuEz)Mr`!+J!MJRdJ(d8N|*3y-1yU949 z9q&8TwYN2K&61SbqpcA>Z&*zUl$DyhiOQRGMvZ-u6YJ=M^GpTU4Z<M9#&>bA_$|`z z@0<|E0QiXMv$D`nmjWd*B{x7<&J5O0qK1?)Ju(0R4o6*E;l^TttCD;OH}Cy}UHa{* z>?(Kz*Y$W44qu%j7SZXKTC#;J@4S>Wdet2ZFkG@AUNZuhz`Y6oMITu-Cr4iBBPaI) zzjxz!-9%AqJ?#60fR!4qB-<5xO>`ks8Y8?&-F1|oCw!rXg`?W~%_;W(b<d1O|8Beo z)K}jMoc&KkeYgJufy^z8xC@OCcSu>|;vOmnziBLPD8hU1$9za>j#KNl5N866Y60Wv zLc>hG7?<vRRZ{i6)s8LK?gkQ^darw2)^d~xG{B`T@@0X+V0$(q^;yGfdANrLV(R1F zWR8}`tKD%fF6`7%u@3~P4dVzdylAhcJCX49Z+ltA{Eshfjn{EHR}P!#T=2?C<czQw z+^_})IZN*f>6jn`7+E@&WhY4i!GqWszqb3m5I)83fqfpB#!|*aKh;AkDoPanrM;@k zoq(NV-C>Dv@A{GR^C<HqS{{t(?<2$w1l77rwfvZmHw3!yM0Mv+D!K*qr!0OborcC9 z72xr=q!~TcYLzffa`<4!oKcOVm+--zb=~U3yFaWb+CJKRi`atg`m64V<<s1X`Qo+x zqAo&}2u-6?kXuVBq@^F2B$gi1{BS2Ulau^lyJsj66?t?_2iuKsLNX5UJf6o$jk~%} ze|7x))Zx<@)o&~$%vnXyAOF>j0{!1vl5iH}>I$)lw!;%W$;_7@L|T8|cr^q}1|P&1 z4Gy=v`w0z*UgHA@GFLBh4Q2nPd&1Mrv<AM1-%Yz%H|tftjX9$P5|AIWV?aGDHBGHw zU+fIDhYq>A7rVR7X#JBLlkXQdW){GWN%|K<pI~C%Q;X1CEdu|0Wr2zMECzZOIFM)m z!Txb*cX<_7=xlgF^<i?}hwb>3fG6B~KFa}B!n0cdu=Uew5EGJzYo{S=D#dM|Y%N}5 ziAsxh)Zu7b*jO7Y)Th)>2Gs6K&1~UituVRu+E`?TAfB^eWNpc=AhLp?*N-2*U~%9h zx#4uLD2tHO?_6+D6sk!?4x0Oz>loyv-{gfZU;5N>Wd{M^evuoG9d1XdE12IDM>~uH zgTj3CvoX62>37Y+T5sH?Y--@Q=pBhMRY@BfMrL6H8ZAxpNbmu8R~4h5M9p_&<@BbO zWUtji7OsVMvJ5_%GM3KOG35p30~mrKL0>NU%!VTq1^Ne7fK;{ss1RU`f`Gcr3<eoQ zBM7{_4Wi+pKYLx~8rf=^0SOFnerCXANQ1aAo3~FhQ2S#3ynULR8mRdN0|fE|#0~%v zgAhd&%Xjvf?Ejx2Vyw9R(M5OXvvuR8_>xXR35=~=>+T52E4)|6;z=;TCf0l7md34^ zx92Wt{N^zQ_+9xUN%>@xJUCNUV9zZoU&q9ff(7p+j$*9}90q(CRVKo-oWUI3*~fTb zB}C~B7&mk7o5bI3YbQ$0FAeTp{S0f<=yAsL>??Y_jv!_Dk5G4DQVc;{=zb5V&S_=% zhl*{LCzmUT^eu>+@o8B=;s4MnVkUZ@$`@YXL+?7OBQpNRQDn`6%gxHM72jd!Qu|P| z(Q(9Yh<>sYt{hXcm6avmqI{StRn^1!Ix!$EJscPM+>k7tT4<tNYG}1k;{9?jEiRW9 zP6wkzT{BGw#%K+f{#aJx0>86=9KSLwyP5!y6dB(AnzWU(EAD3g&1`C35#w1+eKq#+ znR}i!a1Q(+Da*&mw4I8+U~5P=wa1$uzBE)JMX`nq+4E0I&&K=+<WsviQM0M+VxXj4 zp>yR2Tb@N4M|!gY_}x-cx2*cqqBtXC0CeMsN)hwS@-!#<(C~uuA6R51YlCBPqe)ff z2!1g(t{f=jP8%>jmi+mZ9$yjltUE<SP}%bhMJ3`zWGB#BY<fM?U;l2k{t^Ay=kXdO z)=N$m2GcsgqM@NCKbkx*_ez)b!Eb!g8O!rdz_<D7bWRM3d!!GP`ecS@;!{gnE=rS` z@VH4DZeboxC~a|Un%M%;tI%s-q=Sn|6_%dc-TCCA`NXRmpD;79)2tXHtJ4gSD0y%; z+$;%Z7`CcNMNLjOYb{Iau|pC$hVm>3D@t8CL3xjj)g&~uXM!*)izsKTwjYk4mv5&t zE*m=X>NJ#_Nii=!XoAwnCjkzgesER*Q*3pb3$63nK!ADOq;a@w_ds`ON14T9%h)fx z#ddr-OCnTlf|_hn@<p-CUI6PRnTjp(@T>{Q;2;Rb6fK9bozJitzpv~s2HVhTBZa@7 zX5L)!AWbW{@tH381%mT9&MU)uyejT*bSMTTxAZWL`5DGb!epU|3YiXpS-x=P_b+mm z)bTb6TTp_<a1p%7ilWH>+>sPFSX1Nv*(K=h!WH0l@fRF4e(D~8$=oxB7FDu7+)vR* z_QtC%dsk+VdN4nU3j=W;$?TYWh|@Ip=6+#Z;}SHM(#zgbahp;KToh9mv<Q8LNPAL4 zb^%Gd*0F_uYe(Y=TaEXW-%zrTKQdNwOG3X!Nmk{x2VAmGIvM$j*ki%Rw}qYDqw>N? z#?f%1cT$=AZyS<b#UA%szbtfDFM;<83OfOps13<{2ev1lxZy6`pcFY{UB5YYQV`o& zPE68we5onN#=97gq&@D67gb(l!+F=o`T3FG4S!;33%5gO1=AfJv+y?W<0|wQMd49v zahRF{iy0NH-Lg81I$Y5<oYfI842Ra9Q!?ZZ&ttb1RI2C>q?l1Qps@-<k8vt`DT&9d zRW<ZBaU~o=_qg2PTKEd7{4}N^FDek>W<{i<bZR>}P_|IN1~KTHba!9>Am@qI>*&UP z@t~fu0mG<uV=Yz9WsI;EwuJsdo|2Ha!M**O&X*ciu%m`g^q9W;fNR7(PrfTMRv1#8 zwE8WfaLEr`GoQ2065dudpkYmmt_I-?ZQpx4kaP?uY!!_jex38$R+Tl;<F_2REY76b zRQbVg!wfeO8J)nw*dQ`J2mla!OlXvKt?j_|WiI>SQYpxi$yl#_*@#r(^0l;U5A%`O z>z;SIb~RS;{<qKS^r2x*qVrgjnqmur1(D>XP1BnojPv%Iem=B+wd)4ZvIOwUpOQ@2 zeAQhu$XR^5exC!mJ6Ji@`5`7jcLZ97%=YHFL1fWS4!9V)hUzAmK~dy3#69SWS&w1k z*uDfxSP5Kp{*AAO_2rgkCj$w!H}<xwDkbZ3V+O&7A-d6eXm4YWSd5VO!gOJGbock3 zDdFNj7#|PD(2{#34rV`m>owKPxt<(c8-YOFN{GsfwGU5@N`ki!ljb$OYDi?tL)$Dc z9-G}=^<_s){q?3Tb~*mXN@r!KJ-PB*td+#32+mk$u3NaI9Hf@C#~{j6n_DrgY_`sg zBYO`NRE8~~1(UDv@~JQ-nzR=oq%lc~BO6i3KyY|{o%v^gT5Xs|zz?|jEUJ*psB#_} z*>KPDQa4^~8I?OnG38pE!*H!;&|V#InKDp_4qX=x&WGs6n?=F0wx&AI-z4rW50)9O zBp^E3YCh1QmG>n`2{_RTlvdKrrt5Mstjnv#z#Q!p8jxFHvY51%7t-w|eLlXkri2Xn zcv^L;Il$0<uy!HZeSbjR*crFF!miFudS-7Yvuo%Nmdj#Ku;c_lr>wi`3E$$_y}gk? z1ZGKchKDAT{9QBMyb9TK9jV7EZX+ERHX<<M28px0;i&4FWKP<n5cOc3Tb8e$$mL-J zE;Nb!j32SR-QM-2b^<U>>+LUQ{R7ow;I?8{$-y?aLFP*n&ZfrAc7@?4{K?TsaF+5# zG9zM6Mwzq^oS0D7KM%JL${hl~jFWRJ&pub7uHk4APE3r`H+%m)F^v)E`+qE^rO{)r zL7IWX5!q&x)i*4Y-%#hjC1P+RcZ_S%wFCuVtS8J+QpYbyd>*`R5^vwf?{My?y#YmQ zI;B9CBTj$KGG)OpY-J2L&5eTE0DQX*nc?kY`QZ`d2F1@6$9&%d9?m9q__e0Re$IEj z$d>VC@1))+%FxcPL<ay9fZN8H;S!6KiXi%Q{v8>YJt*-N7gt2PTXKcjTP@IqYc;W` z!m(-J+V5bjuWmuj!+km0=*0?jg5e^zB0YDYY8{)KoZc&o;=bHB{ILR;9}bTa2&}2L zMO-L?X#$%){3ZoUDyfxIfw0I?Tqp6I=2OEuMmmek6(x<E;}s6x$-Cb*C`fV1_)UWZ zHU3m<6MD}oODUp)gI;kqdf9z_c`$dX&$4&<L6?2?#BJ2V*jlGQY1LB9BpsiXS5PIW ztQu*(Nayn24&|pJoE;N5m%vD>)_`i-Y?Zm$?N=eB9oED@+$!|dR0iX@jax-ahO)m3 z<5(VU9<^d#Jx=@1#%zqUHq3lC@VKxe|4?rmA#M6ZmEF{-#&{tOEfk-0BEx@okAWoY zKP`S0Wp1Ed6)(feNjCUgUa^S}B+H!>!KMqVyqnjq;X^7i!kLmh2rvA*wHd#)VjD3x z%E``Kuy|NM=}81iJaT41!YvoJ(u(go_qT20@^|6p){vPD?WX(+7<58hUbul%Gb;g7 zW<pW{`e1U1?3@89UAvlXQZ`)p*R{)DbDK}f4aCGNCZ-sR{VbVaOWS4%OadPyS6-97 zGcL=E##3DSRqbK=m||@ZCpu7Mv&uMW7;7-AKiTo0x1)!Vj?I?OhnsvIFCVg=+ZC%+ zVl^@Te4U_Q+_fI^y(Yl2eNV+=Sct9vA+~%N&v4CTZUbH_FsjPso?O(B9Hm$+Rhgxk zla<zy@T@+@!77b}Z-I+yHo=*|1CKZ@$<DCiJ5g5+-BKqg=dS74f-Sc0w;E43bOwhd z1bR5j4^I6f=37+j&z<USmcevOc=nM4ZSODdE*4gFfV-kYxZ4*iNUe(aQMQCNZ!fZU zU=f0s39UeJdo{r_nwB-Tnm4nmYgIA0;})o`^%LgLC*Vo!ui?lqZj>$PVHwrtgV3KF zMk3?-$PDZ6YjRS;-`KwVk-FW{8Ctc_)Wdqjpk{=QF;eDK{nve^C8e!d>)9yh&&Urh zx-JvD9AkK0Q6v;v@ja*D@pUU-L{)+`gx5<VI)A7TLJ|MC#GyT2CI3p4h`HZ#Q)ENa zpk?Q6uhs(bF{M=XtaA3dp{*^vA{Rb-2TqG?sr>GgRz0L_<U-~I;J^~lX6cJJSr5mH z?YkX=?6X~RUX(xAT=42@v4T6^PXW~7?)=FtH!2sU4y*7Ev4?!VldERb{kfyMGpX&O zWTJIJX?7w+^fgKB^G=(C>&d@mzNQBmvm5kmyAGjcljXLe>q>d1me!^$W~F8vk8(pw zRZ6na++#JIqNxB*IJ~;oB2T2@hq`>x^X8N^%|`f4N+6+|yT7vZ{*aT!#(jz@=c!V; zx@$>I;|Utt5>c#}`Tf_vL1u``azqLtT}L-p0VgvNfRKsvA_e(w4$$He%wH$WCxg0Y zeBd`oKSanyF2mqq39r<c&(ai~6^n_guN7!w`y+$WX}XKGoa#D6lUmCAfvvu7Q<Iem z$w8G@Qih|CnPToA<9%Qn07nEzpD^!uZgrJtLA`=UchHcMU$0V&;H*DsYWjSFR&%Qa zKZeNJv$%x^SKzDl#pGZ378+Ls%HX_yU^*c^5P+t5E`fx)PO)Y0{g7nx(ipWXkAC1l z^dPp<V`k-oiW2by3AILMBOHqpuD;QWFk7n_;`bV}VF^)kVUWd84SwE`xX_rKX01*9 zQbe)MhkD?6a5KA;GDA=UALnw?jKW~FjMb;BF@@kiI6f9DyqrL(wbG}J2KgF*e8`>M z%8#aXqmlsnq@Ly^lCOr`)0WEXby#1KR`wlU(f#b;f^WMEA-Y2q1NW=*@J$S=Z&LJ> zudCF$Z?aRp$-bi@6&Le}?f;)CC;C68oB&Sw1FE0qn?_{!fFoz@@uS`q)8q1~V*Kp( z-hk1;_f&Uwfd4G}aCO6xngTj1{CJ|dFMXIu{d6tr*zOcn0weT7lBP_Pf&K=~{(`|p zFAeNS+IVekwvkB(-yR3z-rXd0Y{&|cKQ9fMth!JEJ!a>s%Lz~!7+YtmKfJJ6S@+5z zYVjfQ)`!768&MAzuP4g;mue|8c6<41YTRQmmNt@e^z+5ia!gG&_A((lL{YxQ+rc#x z7mBqwTa>{3V!P2+F+*D6Ls>P!G8BD0!BS}E=_z(9pk)sLodcT90G6Q#RH3&Rv0NG} z7kgP!aGUbuW*w%vVhqn`YWG)k5{MyS<z(fX#=?eE_U?HZ6#&OWfSvPlb=WMEskQI( zZ&i)+Z!P3i7g^mA1>Sv5q_Z}>aN~KB-!WlFp|%K)&&L<KY-KQ{oXtA$87Bpw&rwF= zauUc@K}54{6U){4Hb)n@@*yFWvY}rr3Sm?(rCt~F!=t2a-w*65fn89Q;|44&bll1+ zS8O8l+vkvv_|0C2t*OVvNBi`KYg3k9A4_;G)~^@JENzelZ0D_#ynK*Bm%id{vm9l} z3_+>Y8k!WNx;3hK%%nAN1YTAU$$6fJA2Iup{_(uo)T@VtbX~)~`vpP4GMeAoj-D#v zhm_9Bx#q;Lw@SzFiPOtVNYBCTN@XV{`DUcolwHocQSfQ)BR?Pz&e^mRLw55;x@Ntb zJ-wQ+rrW~Eq#hFj_o%Rh_x+m2Aj6LSP;e;TtBv`3FU$~+^||DehGx;1YS8s{N!}$@ zbWUIeJoJAU2V&&K4#eEOxSD>^aRZf)KWG6s`&*2Ibz{r!e#zrvLGVHH&X#f4Ya%=v zS<$hdVECO1Ssa&s0@3@hy_TLCC=FXby*HOrd<iUS84BwVyx9&6!^b@~(r~HGuHnkh zBXg8B9O={qIoF}ZW_v@AX#D}wK-a3(Lf)}G9o+i&EI;o%8w*2?gH3D~y9TwgBusQR z_Ye`p@M&F#f#pJ5BbPx)vAt!Ugd2)(D%@vjDZ_{_JGIa%e8F3O{3~_RUo1v|y910y zz`O<EU)r1r5MY)RFRZbf`jl#=SIpM5Yf(zpN-@#}Dh}V}b}&{MP!-|DWbuC^cBXGv zmJejt!D6Cg0_{8caBJ$%aZzEZ12B~huNg+R1l7$Cpx8M2XI$u<uX(W-{0KHK3xUUi zE`i+PMFAqpnh#6Xcly|xcsHn42=|w`MoVL%RabhTBh?BM^}Uhs*!E$c=7^WQAJz-F z>k{$){I(QL4fk;WT?KphRC&hh+sY~|P&%44yrZI7fxlfUvHRiT`E_Ms-79zwTqoDP zG8KptKJClxejoAw+r5O??hP0XM0JK?&fMGC4;!iy5RAacL4e+x0r#_)Q+P{QJKQ)^ zsw*Qb){q4zLp^a8V9Th;@T8;!pizk=e?Fd+<$J?#YKho7JMCM(e<_HZ?III8>C|!a z=$2C_Mt7{1d?4@g(z!1hY4b2FuqtcczW-T<LclmFGH;|1R7uWH55W5D($P7BDXh64 zsw!b^&pUc>YbpTy0#!Q=b<spXun`&ET5kOY8@A=~v(fj}_X6I*q^2-#Z2hLzs8Qyc zJQ&PT<07(RjBI$)&B=ks2VNtjH)QvE;65Zr+sK^k_2qBMjlaa0wXfLvUhW_bRixsh z7F_}t2fcP(iv)`sm$i>%xg5tsJuAvrah{sW<g*L$H_()Eg~Vy+)HBp+w@0bI)%8Lk z23dlM$pL)m)aKMKuL{qE@9I<OXM;XI0FaF0n|>xJy2;1AqPNzSVCXj5us?D?=-bbb zu)(k3*X0p#Sf^BF?&}DCGATW}ykkLh20%2aL&ukW*Uxu3PVRxum@Dc~wm$B*7wsdr z*r}rC&0Bs)x!*yql>$Q&py04R6~{k-NsZ3?L*9x2w_di{U}r+Gw(0+#Aw}Xphm@ao zo6*OpAYk^Y@gpw&X9@>KarTsuaO)>-H1jHJZDB)(;b`U3mp6h5Z*=5SAMLK(&osQ} z?OxY@&d%%f7AA&)=8DK5s%ehR!s(~%PA_e|(!&<n(ESC=ibhyU&*-?;zA}bE%f}>7 zYQW9t_dGQkt!3C7alN2my?oiUMOcY3{@Zp^<<|5I$t7K5?}fEb>iln{&h^vv!;P-d z#{m`I%P-Ea9x`=LOY&sS^y;mMEmv;(!nc-LhDJyBd}0coxNUUf2YbxBhf|7}2VLZR z-9`E%1?wHz)x1_vJ}I>(31D5q>UqJ<VA_md)gZP159OdHH)PcRJ_R9w{1aH(|0TA} z#$?YLB`6H;Ih<R;BA!y)jeOM=q`R~<@2f1yxt+wQ2<1^RkgKpFUhqjfGvLeT{_85j zJ-aX4Dp%7~C|+RbF+$z*40TG}Js`RRt+)RS_RnttB;LNc&vmRb6oO2T5_hZ{e36+s zVNsEcozih4Q+Vuvn#z0Ln8h+Tb5gZFHeX!B8%~(Og(y;;r}eD7)-Oka;G(eCFf*<o zHmhH4&%mfJQ1ALR_xqdj^w;~lXQ-dkmw(U^-CksHQS+LL*zYNX$;3$ZWhpv%*(;M_ zhmMQopskyGsLlYy`S{(_?NP{zpoAOPKd7%p$+d83P2goE2Pug;G+dad`#`JVP~v&P zCy!^=9XHJTp8O!=Lt}-MWVEmM=wk!%8T!Y2PoHR~ch|UsXQa~K*D!XQ6no_{&r=CV zwvJ@q9Z%58Q%Ze*<qrS?0I16WLI2mTO;G)hW!qmk+rZynmm5@ZhR>PQmHIym6!)g8 z6r6MhN=z$N-0Rn9(|S!w>WJTjLzgqszc93Kk5-9MEQFwgY<!A%@&>Fi^(jrL!Z@=t zr&s=98#EpRiKmf5pEbKLknm&sRYgXPyXMiN37+HbyQMy^GDWGh6@0Mt_89dKo*+3& zJ+RLNKA+c?H6xPVLElonZR30x$?pc|c(>y9^uhDrM6&-BNJicFPpUb#zUgD-Lu|^F zt>`pEig&)e)r)`)8S*L)Ma0<fgA+br9mrY>=$kfyV1OH~KX+cFHC=_h)%<|J`m5p9 zvYAdo?Qqy^A<;}P!LdJC$I-WP{Nsm?oKtd+^wI}M4L<Q4Wh6z}6swZxiXj(smD3i` zLr6pPQL<dg=!Q#nqXdo%kGAeUGgtP)(QT;CfHc-^gndtjw$O=9by#|YC+cEL>Pw8! zoD<bLipp}LmZb2Z3W#^Swb_<R!KgDwcOU>T)nBLuBOBp_W(V9$#|h&KomztPf(pVc zsnVi9w8|Ia3TD*q4NW>ZvqNpupPS1>a6-NquH<K#w5)U)#o)t^EQ(Z^;0jJfHRJVZ zN1G5Q?du!Gv20#-7v6Rl>2_d_$J40;Fnne}e`5{TLZvpigFm5t&`&x+j<X{q2k1l$ zP5V5c6dM9A*dISJsKpEW=<o;ADO)9dvrxG~T&jllU3Eq^-aIdHJRv?~xRE-Z_an0$ z*YR03a>o0nYijapqOJW<_41pR-J1aVk6cUM{QC(N-4C|~580QTVzv3Mzcp4YRi^un zR6(y>93IK+^01LwYz7k7r~3T~K9|(}YDcE3c)A(6MQxMJX`?kZEkwt=h;V+56TIzn zLuLi?jJk)L{1nduo@lnhzn0`tB_@;ZeOAs`DO`~Zx0>+Jwg^kDXvgPgXy5q;ZT^#r zF{Ofj2Ivkaph|dY5(W@Prv`^^0Z^1=w8+<IBUpgN9u9du<?F?t7B5%6y3G365)nng z_xqJ%%bB;~4VpYg^1}`_v8$;LqJEIDTJUtt;+opctb~2@qBakTPea9ejmdqX`~vMF zi@1iUsb#4K(Js9GWZST^O6I}&F*D-i7rLQ><YDAKN7o@vBuNknsZmCBa8S?%c5ZPh zY~Xo)UsAC3wG4XmVq#@sU6t!Xg#DP9QWYy)gS29%HK5J!94nxauEm$f7id${CSl>D zl(O3>N7?W{$7!3j&U~q*aOZR-rn0;f&MIsz=j#1w6w=A2q*w^Ea)VP~3oGGXhkhlQ zQ%<!k7wg{j>&_NnuMASsWYHpU&t6gEmsSsJDtD~BYBBZ~VsMb$G)!COi}I+io^i#M zN{bIiEinUT^2lj*5AALvkZp9=M68%@PQ?Imz!AufTZ=8SY_AyZ$7&*W4Tp8o)4w*I zWYV{eql)99_txug%Bzxj-u9uMn{g0wTi#0DUrH}Re2SIriPfr0LU(J}dWUEcVro>X z;^tn_UzKC<+4McaR2o*Ay8AgA|JZhCX4;c3HBot8(jq;TKd&tWQ4*IP0H?7rt|(d3 za}BxLnDtBN4`y<}r4`#!ZjuE>9DF%U8#}F!6H)zdChzJo%zm6|rJZ3gG^^m{?XTR_ zVbM1k@7|kZHZI{Iqmxyq%e=A<DpsO3zj<>soVmY69A6rRS5%omODdq+0oYiW#=>J) zGi^zH!!;m*Q){(0-Kn;ifj-$=j986YS`|CL@#I~4*4Du)aWD&$R7N-wT$@Yk7nYau zufj$#_)>Ug79Yv4a5uOOEc5!awyxrbv9-+Q#Blw1V#X?ZOFBg*?kBxxR5j@e!a^sn zlca@tRJ8o#^*xIBcFcUlJfSQx0NQCTpIWg7hAr+m7g=r}a2&Oq1ouusnj%5)jm+fd ziVkqE8NsM1x#o9&Qn75!kzaUJpX(o!%6OS3%#t^$4x!t-fZJGEGMgvl`)p~8I%xK) zGKTT6?G>OkHdJ{^W)h=4s%84j<!ca1bi%kb)>^@AJRv`94IuSm6V3L1_Y+akjVPHY zf~`d96Yi0@8)m#oXS0{UJFH_XTH`ypv)Ywewc~i_#3}sd?JfC)RcQbj7;fjZCj4QD zT$t<WP7CYHMjMX#xZz=4l-IC`)G^Os!SYHySqaJIT6-PCNUJ9%TbNJVkNM57`&nO0 z04U#BA{;V5u9&%al}vP-IY5Txu1i>8`Q(d?*3M(TSpm!{Kijwf)Y~^ttjZx6`JZO# zn}nSex07O=x6z+ep(mUByxoS<8)kbSqfykib7Ufgu(hV^GH_NsvBu1jesb3+*Z~11 zuDKwRh%57?#=+H>>cXpOE|hb_hf+T>H&ca{weMN4lUY~ZSmP2Pv8@FN;eE$OeG1!W z2!nO2BQ4r&&fDG0HmFj!fy1D+?*ch5+|--{q0SX?9a7P|!?+gH+o(qCb$8xt?u`5p z0IW+AtWP!}IKLlJeiEI$c`;vnn2ftSch_RdG)P_v!Oz%^4=-5f4zaSThK(%`@71;@ z<dTNuBF8`9$*xY#R5FxuPZKoZ>Pv)S!0yWOqiE<g5>vYO$5-eN+a^TZBr#*lYa=~! zvQA?tu-Z)zNP~PWSgz#6>I#yS*ogTNVEt+UK>@*iZ=~pq&q;VwV^oNJWlRao0w+|Q z?+ppaP1X%#v7&sp;FF(43P1Z+Y#}nWBLPD6@cR8cvqEQNt=qBSZ69@J2%(xMB4|>J zhP#*nhpEB^n3ajV3as;{m{X)d6_?slA!EUM`r_0&>+bW?l*%ZpjuXOU)T4<7<?Of` zIK6Fz>`}1!?OLDJMkp>=WL7jTS=HqNG9^AIYdRvRp++2%Hv>WSt9@)HPrk?tou(*F zjs@P0W!1CEPgq0RuxzPL{vWJ;cUY6zwl_{3W)y)zq_<HJ>4@|`Dn+`KfOM4}Na!t; z*yseLM*2`fNeD=<0Yw1;0U?AEAP7hay?5|?ojLc6=ey^ed%o|v&*L8!eD~gK{nl@- zz4qQ~@s6zF!TJbse$gMfjt$qvK9fr<R&^}laTlYmtNFa{6kbTx$VXwkyEYnkA7m`u z(imBBq)^C8`z&yA1>4>o=wAPI(U!S|>h)E$?X7n~z{9h;-86-U$n1E8N&l1P74gef z^6Q}`lVXgEr3yf&moaN)g1#xZqmzKQDmQK5;S1EjZy`irc83X^IBzhc7)4xKRMo7u ze$d6*ILu-%6(X5D7$GgXpl{sAdt+Jz_Ikt8Fqu8W@`iX_197XyLPTU7`IB#=!0G6- zZDmoV=<M{t#vgr3a;_}ct0G86uHAQc`ldS%j;fB;Uyqc@xJ?*AGW4@1La$Lu_t-En zx~fqr9HQ3oLxS{2@3~?db3TX=_7MaFoQoG4Mh>Y@h{^Z~`|Nw$xZ!5qr5de^caY@5 z!fo*!<G1qzDi9Xw(R2;rt(V&MkL5$S*GjUoGiYF(5%j?I`XYh1wvG@Ng1*UKbjEv| zRgt0y{=qJ_49t>L&iwI5|GCRmmNnd8sW!HY5M?_bi+^vlF^$Hs6k77#G98duHQ5!m z0y;I<P4$Fu!QR2R8uO@eiaS}v==ZwUZlr6)!Yc^erL3AU$hb+a{wd|SVFeD|AhgrK zej%8Q3Es=*k&Y4;v^Vq8;kEiobw`lplBRfmtb-xTBU#ff@1Z8!QmMJrj6N|I%OBgK z5sBP>Rn_Sh{uRi1uY6-MFQ8o)W4nRuP>lj3LF(fD(u{(_FUhFiC;d*Z=cw02w8*?$ zbkGn_&+oK-bLLLTXdfQ`QZf`<*W!paE!>vR;UHo@aGB{Z@Eh-ynar7NYGTCIZ_(2! zEF&kmDlN2oY9=IHbW+o_sfX^HHEtVRxtoGZF;&l6wJ5|Wch7-)47~{#CLHKE10D>8 zZf7jekB`~;CRQF=nqTYaE*7%>0h=G9w76(9FI0PvbY|mDnuZI;p$K*$IsPMQNRDon z3DzK$@kL%9`U2)<ESfocqJJPz<JODcX9XQZ?A(tzQ4;o`o1j*WT$H^%P~odCA~jHb zD7P45;&KNqc$j{(C|yW9eXeINCV757Kiej_U~sXb#kxB1G5NT8?dGMQsBEQw^4!bu zESvCh32Co$VLw7jYtCEayO8=FD)-27-3vfM>CO1`lXo>$VFeg%aiuM<&^;gc{$(rA zA2}4Hr&+NOLo}z8FEiQ3ataYTdiPg`SKiNm{RMdX`DX-vK7r~f=)eAa8Aw4t$BA6l zJ~@0x>Yu2~JKe^DY58=0oa4+h)AV=2DrtwSR44Ju8D3;?n}hOgFWz*V&77{_2daB^ z$O?^wIw7R4ul#Q}Z&N+|b_Ec)XF?quwNr>H!TPop=^;pHjQ60X{*m0|wWfXTIo$<P zh{u&~2~=sNv4BQs<%c&nnV5jB81Rc*(XTfCH}~)t^{X9olmpnFPEv^6)ZjgP-7P&v zLaCRunW&eqehHDu&^Nrlq3P|LNj>JMnTvU|<$j(H+O9IQ#%~UnS3EH@3PH`f>fW!0 z+OFR<yx>ik7RqO9)TBkJgU?+<oZH?RE+jPey}gU^N!beDbZTfz8W=IuANxI9dd-`g zXkxN%fMlXCg_!>SdEFBF<6f1A9N);sB0RlBX=XYXwi=?3YE)+M?qP@fxcjf=S^22x zt96aN-kf$DFtZ3@X>TFf%DCU}KI;%hS~AxejI@?pzz-5E(&o&(@C@vT=*V&~P*EsH zSbqsBi(S1rRh|8NEg6?0_{tSZ^c;G74L;5X!TC=ZFFm{W<hQ3UfBu0teAcVZLcp<M z5=$vAro5`uGc**oi{>v1%3KiQH6~$P^{ZzWpv;Fd&!_kG$oqBXRRi0!H+Wob>*GC^ zGM*JZx)P|a?}r^w@;FzCAf_az9%(HvFXa}Z3y}l^rG5>~D^T5rHLo34Lk2Jye(h=r zdsP%y_l(!6ZFMHIf}GpNe8&B?8hd(6Xq_sZuq&9cZ^<)R^G4o8Uf#5k#?Z;l_4v!u zpKK5MM(z)~q-v9*p%sJD^x@-+7_8B>X$%Rh*d*$jP+XGQzyJn{K~r#&MLy8*2pt#2 z>$KVZZehRX=jJ_q{r0@e#IQthWE8oT9rMiK$Jd<7(63Z<a8aM^$inPAOf9&ynT*XL zO^7qab+_adf`Nya*w<OT-O~z~vinqT|FeB6;{R!*`bP%jTM~q|$?w2sdOyT|J8PH} zLwi(}!nUv;_7FFx)+XEc$W-rz+G6IxU7=s7OHNio={XkLe_v0KP>_x#`m4Db59LgP z4#zlu4Y8j(Lzr5t>O*h7kv3Sc_jtOSgg7+!Ih8x})BKsoT=pwf3d(?xp?gycGo{G; zg1$5Z2u6B}C_Uf(tbFzRpQW9?|BK+2xL;H%flBYfVmU=JubxO-{_B5zjd=1ujg0ty z<j(C%x!;buT2D$FLo-Q_p3xfkC$YQ$hZ^65P}l7XV#*Md)jpvde#B3oD(qk}Lo`B# zmS3sHz`bC^;XDb*GSGsZl5V_UvICQ@okbU?86eVoiV%m}+uu*I=E?bVOtx<5FANgf zOvNmu#>#~w@1*a4qWax=cHH+<;rV`&y+DtG(v$C{@KfK1Y0xr1(Bz<!a^&qwbq=fg zxBr0nL!fVgZ>idxGK89JsuNNAttZ>S&xG$`pNF#68qG2-!~Fa@Z~HtAoT(oI4wF0; zTho$E8Z|3Ia3+$!zWJ6x`j&4|Q7I(CT{SEwgNUeZVLg1g8>|GI#*5@!f0o%p5b~`~ zZ61JcW#^TvFU3HY*v$&OyVURB9<$^~xfO{a&%K2b3ybC_P_p6vhs=|3?V@xqD>9q; zp|8s3rI@fcJc@=c<FJxiT;a+Xm;OSnx`t|sSJtFK9!fOtJS*ZeC4|ReU}u|Po+fO5 ze<$czOCBZgN@auE*vZMg=o7N0^vYH4D3R+^_PPc0hXwoB&aqwR{okW}`hVnn71ZOI zANUZ*n%j!Oh*{T9JSR7f(&#BsgPYXPX_>>a9_aP-4<a>FVr58en~N4`vp#tdp8!^B zP`X$wkl_0Dm%n?XZ}rLk=AAUqYdoxt3!0l1g#q@t1ZKff+16XD*g37{)hwV9Z-d8< zlRCssRAzz%QBWRE(GTO)NlwK5C^x+z5h6KF$W;^4fEMqKf~b1UJ$~v%1%3R18*!wO z1dBg;2-lhY{j6lZ&bT$@#Z|dZB|>ayNN;$CeR+{w*KDeotyl-8=X_$TipO5nzUX3W z@DlN^79$rsZ>kf&F&=DC7#gIPP^QY;?>JGH;*n->-zst*Yt~?V(TnOb>#Un$0kk{c zm`nfurC)%9&~N_mTR-Y=&e8DOu_znW$mFB<j>Uv#-7%RvshzH=MDK0bV3}G{9|uXI z&+5?xUV@Mt;~S{mCy`=SogTg460TQ+wrtRrLaB}Kixs~8<wLi`6W~7clZWR~w}*dV zLWS)C%?7w;&a<IZdViosUD`UQaaG!&n`ftC++fa0=PQ-XHbl>SE8<klk8o%{_?2p; zne;MAw)`6EqYsD{^?dQXu<^G>eL&AZ4WL>D@H^m~@Egtp75|3|e~csPyg=`a&Dh2D zGajJ0f*(3iQQ*#ft!-_IDH1B?CWzx_*?Q2q3b{5ZR0j+sfoOc;Uhf?x&CS$e5sp%^ zc;zz3pJ{7CSjuK;=dTkbqS*)L(oVYY8&^B2mjjW{{bci|EFFGN;q3PjC;H-$hVnrg z>SNPeUYI=L6c)^pR#H$v=oqc+JM!qb-?-jT*8`H7N?v87DKB3sCW(d*xaBO{fOGY# zceO~fa`4hZ`MHCxA_Tp~T7Y^Wp6Z)_sD%nR!+FeoBOu`Qrf98~vtmu3j<vP4u^4mD z3(taVsL60cahaoWR`f_i8O#{Z#3B%$Xc53XTmBmfD<$)ctmDg_flbv^V5ikI*P%(6 zWa{Cuk8by}OXJAg7b-cG=WUpt60qo}9m6Y3CHe$5BqM}w>j?&wuC)a8omz^3Ys1XF zRa)K|)=PXgl=El?!>DVAeEjj|#`l&4cysNV2cO-*h(ved83vUniZi>1A2oM{&4Xn| zA4+lFe$O7kIHhAawB$XM%A%I2lc(jDc;@am=9MKk7%u_``n{orzuo*D#DUw~894Ot z3P}M62*crF){@=e!J$bvRKdF-E$&Kr+z#i6WVLrD@gwZ9B1$N&Zz*=lSxGbS9iezN z8AvwGW!_*C&~g%#I^m|2O3xS?rI*C+N#*ExB)u0~TO(&r>WlfVrZ+}tYm;KEpA8I4 zCzNaETSazA#G2Ja&+U+R9!rB67&%`5z`oCbAbVb-G^Mb%rO&;%%JlPXC-GF__O<bz zkkKN2sUv@Y7w(w;9fPzMBbm3?WO8Of`YY{?g-;tHbMslv?r2S2pcHA@pVv(yhqyXP zAstPgUFd!0$pY6UDI!mr-#)*zBI_Y9Tv0f42W$ZH&@)Z@^9`K9w?}Z}MXz`0WOEA* zs|T|)e@6>DD`dXv&+O?}QB^GTZlHQ7Q<RgClu*w7cax&wcRu^F>E5hhhbU9@hu~^z z`}KmupUJd@l_gbpzlzQrTi-#IH2;K3EB@gsB7*~Rxi%xlTWfk&nBMLv?k+Dz(Fm}s zUp{=hYpW8n)afvk+8D#1SM_NUN`!tX&c_wmZ|F`LkM5W@C`U(pqN1*tI>~MlWFkQg zs?6|qRdp0p7SZC`t|;f_rnh{B{!-fgKiU~a76;F-#wSa<<7t`R+mK<(e73l^xE;h$ z>S2SbSJizX?Bp3^(}~_|z(?}02i}qTO7&@7=~w4oQ`~{k1L+efQU4ka({_{N-2IQ9 zaZ|U?3@p2~mS8g$6H~`l;lorQuo?W|v?YYD|E)$AA#;k_l{_;DmR81eJMy-Cw$4|| zKRRB7M+DSnxCvgTWQjj2ZZ_4Jx?%ywnZ#$1$l)U|sWJBhRs4>P%<jTxSiJpz3KX7( z-y4;_zI1OjL0K_XNRA2j*r{Bop?8mit3XOWZv2=}!SxQsRX?Z3_^eLdEk7=uxl95` zsq(}5&F}_|s7Kr9SWyq5Aa%$HOsS=4sdd&uyu<kYp&a6o!n=cE`XvL;?|N0l17X0* zjRy$!+o#%@Q1?><gI!ghxF2S}Qkfflr7Aq5a0K7wtyJ7np?gida-`C|+^Z*&a>*?= zJ|Rxcs;RSNKQ;ttnU31nI>)OWf~PHW@og6cgGDb@m*`y6DB(>Q>joN9XgqG=Y5g2j zcc`O!r8}JGJOrgM{1l1^6d6i~HeVmq^Re_%_rHl$C}UUzc6cUs38<$$=dT!F08H~+ zDbD|_7`^1;hrZ^avYMX<!#+-=$Q5fU=QZ*fvuen@Hk?clId2~7E$@n=P`)4@;Ni@u zFNps<hlwN_<>3buVti$(ad}?v<PAc0SM!pJF??0ezpTQOc7Bf(y%gm<eTc(4Ie`ki zI>;t57s4_8jP+*;38kL`qGuz}@|J`mm0+j7+Xk%tx;x7a))qo#x2E0@#b|4v$KY#* zNYPDETmy%g+Tr`^Otn0I3uP-#TDKOwK6K=Jk<;!%!a+1<2qI)z)~gx!bB6)P9i@sf zJNuSFjEBi=cl`WW@pMGDyKPC??y%U^X7ae9wV^1ds&tLp%4eUr2jkrdk8Tpx+Wgu2 zpF@A2S%><{<dw~6`ltZ*mn@+UjhJj$ZeknMZJ4N(D4wV*yg&Yf>`?><dCAnMqU27j z9bc-Qo**;=QMEZ*L*v|L)z$in{y!a(XYiJEBRzwa$4-$j+-IRb_V9z?EE^>|%U>9) z4h9aQ*wJrmKY9AsHf9)_dGa=@N}H0pf6l9Yw>Ggm8B5`}S~F)If>{y-p!X4P9zl+9 z){bjtDY>j&v(B2SiXv_(5?KFYW}xB844}L~z2zEFQe*pj*^F_Ba8YU;Pdcf?&qF~| z1Uh%`^(#o;BgNS1e;AJE+i<M^Y`7<M&+pF$O=@?WN(&gq+9!T=;YEwS-b4H_B$|K0 zoBzyCH6uMgoJSBJTG%x5pgw#%2&(S0g_7_CF}nECQ&?#opT9TbR2J+eXUT8Rs(4)t z=zu#FS$8iDfIU;ss7pMc)tuI=WraiFiF<1iz2Mr)Z~@%w<2q%3I|VJ2VycETN9s;D zhsCX%H6f;DRsO==pJMGdtFPsArON263KjjRuT*@7;t2o=^8;PC+2H#OWX~%U{Z;kA z`eJo;uQHQy%I38c)&V_DfQO~71BEs{0zkYxD5?kLQ0+MZVw{mnIef8aM@(qEmZIb! zb=r?%L+_(vcFz6EP5bE_5>aG4bOo%XWw+EE+_Pu4D`J(&%aT)C`A{GAVi(8(goje~ z?D<tx78Jj&MaA?Srk0gC_=Ab*TCG2ZZAo<;R!q2qZPu7f@lz|#g_u-r=QC#^j;_mi zFtSaPaN)(<<a&?sma9J2Yn$NaqlL*Oq4Wo{dGV9-kS2M<L+{snMdJEtca;*#>pm@7 znu2HLYn$X{S~Pq^9BbNHH10n`7_<qhzUmckar5UOnl*)iu>p$gf*5pSx@!8x@GZ4+ zu$@Q3PTQlKuqT%?P8G6$&%<cz@PKFM?aXb5D!e>mZGgrpPvyRw!}5QhL!p9CH`@V7 ze2mo5otRHevJn@bw`gognKUyMbm2c?X8AC9Jm2Yw2M5)IE%u*R<fUWG1@jMoFLvkC z-ZhoIz@y90NR3i}gZQpF%}DPZanV4!o1J7vocKpR^p%&I*5@sD|DtuEeq;MwAu2Tp z2&!m%*pj}N{Kp{CJLu<0FCK62rs{&;g->qM<gNQh;}%A2)(uStJGrieQhLB+-SItH z5j7Ho!u$Pp*G{ua-AaURLeVo@XAT+IG;5--W1{=pN5M#mze}s!|M;It3vm<3>B+1| zTC)T-gWXjc_TkZmsd9_?`}!7T-6o+{E6OEtS+~MN&;2*GJpNNH2941M^BS3yP*&No z<~x?Mb{12sW<o+7`50y`v0FO#lZ(q&Mk5zb7(PU%wQ(eBfY-8stJ!nl*4uNB_V*5$ zUk&^3x|t9~;I8z-b+6CglKgo2+OIz+{T7qV!;YJyKeAda+Nwf-r791s2`@RkdxlbU zIVq(emdQ}m*#?LWwHE{eYsGp7O|QdzsaNj1t9PX1NvY#wewS~RSYGsln!xNjSn^5| zIxMjoS@tRFnM%Ox&UxQBw11(tcU6gieEZw_X2)C0EGr=c4EZRRXP~R+dQf*mKoVC+ z{Va-TJa$ua6tUKinJ@+Eg(do;wZmqTV|M-<GQWbPP?OI<;^!Puw9kQ(s<KazsEtq% zpWvv`D$XLMM35N4yU`aqYg;yIPC*UUzHTtPGWZ}pCOXjE_41Tc#iaq1cqr)Vj}B#Q za^9%;XCS_d5R9+rgx&1NoKY118HhuALx%N<3)At_kk!U1n5?$-(RP5V(0p;67^!>_ z8btg+j_Xvj^10;>a~&^OH1d@kp%n@4MSXn=%*Gwq+HeBjq<@Vbx2O>|GRD$rPG_EW zpbVaYSoxm}Dt@K<VSL=LWdfGmv_HQw4DL1Lw63xJMB2wD;JM-=>lYnDQL)cJymUe= zBfdiaI5A^j_5aDl#Q$Mps+Sf?rWFbaMDbZjUMgp<;I@;z4ysVOg?}w}<d4Cw3DQQ2 zk%%Q0iCGW4R4*;8Ngu6-Wv7J*yV9h1>e(?GdKw6eKDJ!hyY`3Zubs0)JwgDd{M2E) zliHXp9e>t??HhYs8YOKL$__<+ON0kukP`k9jByOKCgtPagUJ)xFjiXL)(ug7o$G1` zMW@xqoz}sM$k09xKBX;(H*gG$F2=-#>#g<%`D9(xFN9B{<I{?!vsko;Zx|_{PoO47 z?~<>pxGe~J?Q~2A>rI_2&%EVvZULnk0^%{^pti2T8r57^c>N`mt$)IGcbrD~){*Hj zi_lDZ2Y=p7wyDMYy0H~C7p|{VPoe*}PKW<Nr~Q{Q<=2=|W}(1kMJAf%#uTtZ>YBvn zyupeB(m1Rh-eZtOEUUOkG}1FRDPkUX6;4~J0G!O*Sn7nS17`Ag&EBjJda7JDys``u zIkO5m3m?H-As1JC)I0B9+ndKX>WAGhR`wX|{7Bg7i$S|GmT(!J$zTK@bzjbr@lI}l z+re7+*AvQ4Zf&46LO{O?aa{b@i_`f_Gi>ats44_tLlr1406mE3caGN3V10B`Y;a)G zF<rjNG>p5Fu;A-bdZ)%D$%H7_UQ6np4-$c$QNO2hjGqF^)sN%;aqgJ!=g$A1&ixyv z*aA>-&p^M5{5w=#k1DP&N=dB{-3wx%ihZt+f&~M|57La*8^#!EzwHrNm`rVr@gQ+> zra>lUXG;X>YDJs_>!<DLHJy7NQe?Sg7%RpMoDr_NZvefwlU%WPq}K9NSW=oSPkwH* zJZ_hM{&0P`hiOWCLafq?!i-Ba*6rYB;KsFW2kc0?#MU`Qh*py`a7+tUMibXd>IgUy zVLelnzzYzw{_4sHVt$CCX*K1#nVT%3>@>&jr{FDG#uE;kuT(TI_K$fxm@~+$uyp>B z{4&$7;Y5oTa$W%Wqa;c&e7Ad>%QnEBehNR*x*!{RcX$;w8U^B3V^`F+x`TZ?G4wp# zze6&9On#i56U^~SvGKLfRzpSXY>P6Zc#h0hD!;h5j~i?q!v;H{1?jlW8jtdOqh%%r zkk3b&8mN^Q!1;9HcT;Hzq>ehHEk}g41${T@o&E5Q)8omYGjIPda$l(=fSW2acUR6B zpB<^PAz}=^w7%%&2r6}{qfs04t>NOVerZ#}X2yBBf%9O1e6eJ}WGCYNAiF6ccc;DX z-l(~-=w7-X!C1y@8_!B!X}Z^LQ0qHnLX^X_)G~*h_AO1CnB-yM2j=^;i0Ze;|3>in z&2ZMHh4{tt&!qhWD}K~O7*J>?n}y8rZhzf({jhiH<$`#L>sEbIbtYJ4tRr;Z3y$Qz zkuo%iyeDLvo-Ik(wMDp)P-5XA9?&T7FQ}0y5D$XQW85VHA9l&MdLesKVoPINBRrqR z4dP`~f?C??g!Pbo*7u!Pn1xgJoUKLtNLrXl4zr6p$mlsWTZ_*_{n^TSi0465(;gY_ zezitq9Jm(p#fxA@=+e0*JqorX;rv+MdSG1ln`I-`$;(sQ8Ld}iu*O6ufiwMNqbd1g zgrX#kRS|9|S(UF(!gAZ&ZW@_3(-LqN{ijyk&dAF`(6#|)YpABCC?5+(1KfGOOjAY5 z{}~2&i&OsHXES}mQ>qt3B%p&b%-TI6g;~`@y(uT_8nR7dn?_ke+2t1#;juHb$cou5 zb%pDqeoXee<{gHJj*0r^9Ius-=NH`9uME>ZZy6Uc3la8c?D8(KsMXSwWQ|pW*IkU; zTy!L`!TRYeUGTCN!hQAVdf+6#$l`%%I_{C$;|6OYgF+ct_f%Wfs}9nRHg<C=s;iQ* zs1`4R3}^9SR>O~cr>a&Su4mr{#N`TzEBh;zeLx!_2=z1(#7>J{r=33DZ`?nbU;h9{ z6(@pjU(JiH|96sK<3jmH0$?7>`THkxa+HG9zEV-|x3*0ycQ%VhOp%ut0D=D+GTp2i zx(}FM=Hwxr>PJu2gO|suFFv=#ci7Gl4#lS>HOR~CfRJVWsgRW~Y*ho2b^fQ4MQj?c zaQQMx>kF0R!g3Jajdfrq0Svi?Ipkn!^(O4hQ|IBR&T3`5|6m<WDM~>dYoAPzh<vY! z^=Kw5UwX9tLCS0c8gwGYD7)547xdp{`}ps&HK1Roncm-TJp3jjZ`4rDp_LX%Ur8v( zZC2G=URfi8v3m*DQNSeLYrsZ1DsLDa<G$%b-gosGV8(NK)%H~<68Cl)JN6!BHt5Qh znZ5eg2B7(O189cZ)!F|$EeuWlUHTbL{|5gLdA?&b7Ske3pW#WC0J_C{&yU-Yxxv7) zL?Bcv^Ksq${INupl3D++1FCOp);H?)vQV4xAWr>4w*9b;<KxNTZ;T`v`f{)~i@s2_ zQKPNP+`XDmo1RwLnzh)m??4tZMjI2SjSLJktvqX$Lht3Pq00!^8<@UZ;WBfbR>vs2 z6;>ToYb=Q4qK!LuI8-8aWn&Jt77OAM=^x7X7xpW)0Lyg1gMuWTuo_2xIF~`~vQhUU z>tmG;y2CH*Hx6X*XDk=iQM+*<9y*1V4Khz_P&uLxb`pU@U5f|(b`8#Ns{%b+<$Bic z)zV9YjvLpKww#4b+ay$26=xIQ?!+Nc`UxNo;~|5Ll&xJw!ELV=eLPb4uWx@x&U@c| zKkPdT@jSDO`bzaaV(jF`k2|5azEUygbRjOJqb$NeTuK!Br~)Zm(OpUmEa7#fspYEF zYGI*U`2t)5Kjt5u<F;H}g7E~0xQQGeXAv8VOVmwwH>%b<zEZh7-$$KA120cC19l|* z`*{Xz|4@PMR1>HCYT?eLbGgDy>a?g{OP0$c3rI~AL-d@#iP};FSV{al7V5J#8kzIF z;Ub3#sfs=Q_+^(}$w0WIiT$NXtF)3+(NfD9p{7#lkqdrpn34yDE6llNxm?GtF5N(` zyp#8O<!)83F7aC9*KuwZnSAG61;{FwmRH>8<Sk=w6VpahFI7%*bP+=B)^sg_H|Qd3 zt4qIfqum$eJd##}d35Lt+E|=OiJL@KQ&2fb5C>h!ca$JQ{^tHT6h_wnB^Lh?ik<H` zB-?{GPnrJaw#QJMaUd?bN8dThyK<<OXpo@nmuMetixr`+rK;F`WP|63%G~HN_2ntD zU@6B>A+-<6j!dV06Bq8)xFx1LO0Y)m!kD;bKlK)(L=!<Qa&3|AUQL*5zE+sDqh#0| zj2NR`INERE?&wzIY^zViDQJPmX?K=eokrhIDLwzja3kLs?hK`b1aZ@KntkX0c2SIx zpxfuw**yLf0BAyCA9WG~)M-)LYOyJDP=T3sjZ~DV{`DDEm4H1U0$BeR0mKK`(zoJA z`hl?Hx8rvKp{Hy3uT=6)TbzhjGl<Qrdx#5fLwI6a+l#hr+Z>VjVX5)3Jhw(tT%5%{ zag$hV+y$$+VCVH8O%^e5#yJ;SVdqarkH-t#L{d_ZP4pC9N-~5G$+7N!)-y$;3QIze zOt7sbjmp`AFAO7O2=uZPIo@m~J)At5NqzeoHSQ3-j)t+Ai}}Qub_o{RF+|3jP6q-3 zz`KQ)&ff<BH?w)jEbrFWeYgL406_PT0f2V}ss?cTcmF>y7XNpQ1ptA;hyQ|D>tDi@ z>H7m>a&J`rB8G4=oB=p9|4_kDCwO6H<)f%qcAe4vlO(xz_uk$;JI8?XzbF)yn*icM z=t_Ra^p2-j(NF~*T}vNiSYqXrOA%CB(gC43fL!ZoHYw6B!nPG;=gZa4x&@phXhi8) z7nuU1Ib4GF#5L@Tb&lRlJ=@fKII6h&uUrtxPIPR#2P#{*tR6UWrS1X*`t#1R&P4*a z^d9O>9Ej=Hy*<Z%Q{>L71)(6t`t_84(_Tm{7vQ%H{`6Z@D7P38554dAK9=4RWrGCq zpc-O;G|7%G+P~7g=12DN^5JOV6yD7E2SSuFDae;K8?$SL_m}^NI`<*>xit9#8#Y^` zMi1-dy{DZz>pz?~G1ZKp%uw&%FW}3q9_Rw!uVIkqeeByNG-vQo(i%&t-i^6P3Namm zsJCCQA@Wz$YEya%>FFzW$W+2UPSvg9ZeX3>y+bFvS7VG9zi8~I)ghBW7tWpIMxl~H zGW4V%ubV%$XzcIS+&v3i7(q=YgKi)OP-c}Y?#mv2<##eMed+Iw-3P6i&-fj#CX8@a zFg#u3RI_njUUD8Ua?Tv(4|c{1ZxxRSIUUi_2P4;)ArU9reF|1Vxo!Gl*zT`XBCQ~7 z{Ex1ewYAfyP6keN4>U8zahAroYJzx;Ndn;>r68sX*<BJ|jxZ)<>7j|jB*ZWEdE0D! zBMq68y)`-tgSp_xh5?~FY5~xJk=^tko`qM&P0?Cn=6xWE@)k%nnAa<b{{LFSe2me| zq_9@KU!5_;mCXvbYrcZ)jkpA{$okP6dVXemQY^#YF_@Rmg=?<~qNn3tVeqIRTwyHn z#=S01%uw5|ZrZC+go{FNcAYl3bnaDAM3v?4+9L>DZ#dr2s40%a+_I=n|IO7`LeuZn zm*QFq6B_-ZdY?IUL|3*7A-xI_zvr8cxcSJnnN@lk^~tyw*<^3w#nqdKIrCjOw`LX5 zBV?!htSnQ!K}X}cK+m6T!PQ}0@i55+MZ(BY3W5jK$2#VKk}hVgLUt~}#)YoZXYnpF zbFn+F4(m1qUZZKerdIyK+V9EUaV6^;j#U46?rUcn41824tNP5;r*(H-GL+(a)O*f7 zw=}Qroyk%|7JEUNiAJ8R{35h`y_izF)mR_!0=~Z<*HPBpSDR}iGsNs5mW3}u)bXAZ zXE=)3YlKhaa7-N<;0k1cgXQ8~<;G59r@D3~`z{W0U#MdtOOx<(;4YtqX#0bKBAGe< z#2s7HjV3Ny*d^kkaf-!DEp?gG>jN-H&nHd~y<UJoRsmr{L;e@j@2#YE%3R3d02}FI zcvBRPY~Eql<A|!we&zTZ3!GL6pc#SMi%hGzlrvM`fzQatT3WZ=&+Oe;`)|~eD(fs< zBG*%PKD`y5Pn5QopL=HpJiW<&y(}tLGIc+!mu0bwAOCS4hQpW-r{HZZ#OcYA@jo__ zj^=)c?lAe0gdh=M-NlKd?MDn8*{@6_>RJ>{&NfLcJ2{!o(kkMEI~gd{DGJ1fKtAWC zSv=hyQiwMmFZDHz@i8>jO;Fz4-oExIsB(p^bd<tQENlQ!GVU4kuDj!)-}@(%L9uFh z0@^PT>hNd*_Js8kt>+AjvEhV}3vZ<*=O3m*axg42IURYO|6ruFTA<A=(M3nD&Aq6J zY=(A5rGG?osO5-BqT*Wj5R#v1L-#u&Ib=mMa?*Be#lKqmt41>>N+S`GZ=u!j2v4vS zKTcD~p@p#P@f29A3oLL*?K_pQ(lh*{1U~*H0h#^%A)j#<Yb&KY%0c^YF%^~AVk-?2 z;@L-_bV~KUnrr`G5(+_bwZJ0`mx6rbFaxk?p{_*P7L`zYU2#9_=jpkfaOrPAknQ~w z2yxHqs3Ajw-KC!^u8i-X)Em-2*!$fOP<zM`|4U<f+%#B=D{*xGt>i2Oqm`o3U|fz~ zQ8+tTH9qcy91n&fJllKXckXGm_nF(yIazc6O#G6Z%>0#VUgK@Tyx+K~>9Y0Bp1BaI zo7$9uD08|%4<ZG4GFGC{ql57uy6UZdbW5w;TRsB|N%cj$n+?aj+t1^D3#)BuaOMd4 z(YiJ|J(p;#Aj@|$s;03}$aD$%7XaC54Ptii2>v<Jq*h6(PfUoZ-f&LaQCtzM3_6o( z6NDz%J^aviq#C?Smp`<(%mvPyyaG<*GYAeo`S8kSrHR^AMnR*-rOYVBRc7Mx##Tj( z<iuN|R1A*0z{Q#`!~8+u`0qbEg6cPEN{ZK*q!PwN<J=fkWH1G3CYnONJNPj64!lFI zi^k>VByIB!#t>!l7G7AbdDuaK#CPbK+3fqzkr8eyOv!y_rpE{Q;N25m4&3gP@F^1} z_+q|NX;de9y$dR=wcWqvtHM4cm9BcPG1{<C#$~Pl!MSWyKMKT-fUbX|gu%a2!gH{L zliB@xWX+FhIz_NC<pK87zXZhNq`97*I|R?3w8K<e=0#W9N3F-_q%qP=q8`o{dy<i! zrcy4w+gb&97Gg8+@3^<{nu}goIg!7y!P6&Bj{!EIvI=-U?BjnCa}SpawnqRGz~-Wa zBInfl2b3d>r&w^U@D;>7|Eb`6oo>3A3vvxQ?ps+e0yxYPjkJNp@T<|>Y)!Ws>rQiu z^E&FDOI{X1i@i3h%=IRbGSH1Zy-}vHtcdX+-e|~Q>IE2KZVh0Bkt~1_fPOD(8mg1{ zpny-NOBb7zr8)aCX_p7^LJt<MU6WOow<53w&Ft2#qi$q^1Q6{EFMJ#imU`7O&|2;B zkldH1`UxKvb5ppZ!q&hE#z&WC+^R!h1Z%X=FtUavA_cjbLw*QpR31i}N|hz{{Gn~? z|7)@Tu+}iVlWpZEy9+il4OOusJDoFT1&jXhp`t&T00B^e%+J(K$KPgT9e?tE3VmyB z4AK-=H8?OO_L88kSePgcRkRv@geBysm>ua_--wZ#gv>4uBLOxTmU(cZn`Im>z7x%J zuR#YTH@4shuXwW2R9EiKesSuynNwkY+Ad%RXdTWkfFTqKr^9?{juafK*VdHp?RK1Q zARumTm?c?;n%rfb!jLg#3j5r8%<vT6Pj#CyH2K&<GT7HKk0>+F1#KDpAk2U7i;tmp zyjt77(1()KpCs<a`vj#|<X_Oa<sz=x6rdn9!GswfOffH>5v-&5S*E=S*L3t!)4*HR zKLen^{wEavCLN?#p3`DB;$8W7<IP;3`$-+}4CuN;{Wc|fY?q)wCt{o9ie~D#sJZ7b z+#J<ZAT^**`GU06ACZ0q*;$+epCt`*`DPInmjkQ@b{&&!=Dqg)h$72<<Z>J{0U!mQ zH8Q^0S=jeS>+JM6C!+2mIYq=f0dNkPWiGLJ@lE*nep%<)WB0+#vyu*W#{{ocF{_4Q z1(98I*>wMlOA6@5hB}>;1#GA0^T&~GsHfS$4&;>DQ^rE2&ZTTH#Z`m!r+ZkxDQ(TY ztbt`W({d;+Ra*UYUeXq(bbDDrF$~OS+9Oo?%^`m3Nm2V_-EHKm@-b(L7v&sQV;#(J zyG`LiA4NRG7>Tio`@=u{D^QTvUJ?-h87ib{uSE=s8O`;{1WsnxgCx#Ym&ZGv)!he= zQHtk7O!|-%qfU7Sh6YOad`8xtv$!;<VpF`4vS({Bdj^M4Bl1187(PhJYO>!iaBXc7 zw+!7K{Yv#w@5mUj>fF0YOph@ccT1@~zWZxw7H`79T_I&gnhET+q8-k5I^}^iNJs2- zk*)$f_v+`xWe<I<yEfP{KeBKNJqB}}e&Xcl#rN;5=HD^|;(vuAlqOGsh9|@0A=W9$ zeIX`m$kS%Xmo{1Do5hdUgSnf(Ql-^|(yjN)PnyM6PjWArqLe0jxR_0sRrr~X^``ai zg6qsoPnye>ZMgfnsXrt<N8x@4cs2`}jV!ZR9&)fC$o&l%(nl>w?>r)sqtu~vWxLkn z+>?=v)YIi?9VA%9&{F<t)@Gg58n{i~P}R+Hxc*uHZ?V_?a}4C#`gKFE!!^SEaowIZ zo%ykIv*oAAsB>l92g-q8sU#sMTvOSOqUA(c<ws~<ymiJ!6NNxtgh^QR@HCc?hk2E* z$xI<lb`IbI8(x!_IYfd&3Qe(X`$x+31y);I3mi&oXWZ|GV}E)!dFl(uVL1f+haA@b zk^`)U<4D1}bZvPdRoSy?Ii3s=5&45G^5a~5`t{Rpons$ae?F44EmQt>@^E2%K#_<j z^`*wvn!C=_U01fEaK>=bY1+|DG<SOqpCAgXh)7m%N4@Kd`|jK~G;N>x>cEY#Dpu+; z`zv)Dk`<TYM!)=w^l4Zp*B3N^2i2LX64|~W8;blq;!JdbAQJc8STw2va4Ei-!@aLm z;@gUH8)7)Q5;5zqREXXaWCCwD)UTvrnO2Z%X=U_sE}@IPK*Y3;)+UR|WVh4K!C(K3 z%p!$1FBlGH>z*VHRhBElTdFM$xCJ6RGg`~sE!fB1v6Yo+{g|wQ);grst3M(L>+eQa z#B8Q!<WFi3E@Y(H<#kUwy(d$e1&blIO9D$%D1a>9jm^FpyL{6zij1=>mbR|!v1Muy zTw1vI<-un!MOK@+c}dht9*FN;8Hynvcy->6ca0R;`MJ$3aA*5ZACbRk2A?S6u!76= z*(iLa8XbMfKK<7O<{y(zjlGB2jTDJvuhDml5~c4wz`@w@3PL4O1)i`h+(ZcbNJa+j zT191N9mxUGERM+kbfn%dF6LR)w?4h1{Dj)BcWOiyM+>bZj+rBL7l`_cQ{x?Fy1d?m zi{Z<Lt@&tD-&#wc-3UiZ{poyOTX$K$Cx?aHJW6+^(f-4E*N7H+8^lLa>reg*Io?3K z?TR7xvN4D9=!xWA?*cCo4|%Rwaj$L8npPePi?p2v=U9>!TzR}RYS0Ym4?|AI8lV{T z@W-Mjm*#I?T1CVuJ#hu9z3#SO@4%!A;4)wK9gL^@JN-PTc3LsC|8}|mFvINg+R1iU zPO>ulWzXPa1nMv=Gl0=F*#sp9#EfSeA7VF;Z80$TJ8eyPlgt(138qE~4HFL7Jg9?} zpN@`M6I$!Sdc{p$q;sgfP=18}(m6RYHn7>59ca;kt3ARByQJ(IH_Gm9Vv~NJ*lEj+ z9*Lw4#*^oZ+I3qqy&KEfTpE>!aAJWCsR|;v;b6VJj5c^U7FLEk477naD`zXwCi8+8 zk#oa!r<Sf&BR!h)L_iw6f&(mo&in5wqv+5;ODI>AG#bbz6lFegDpbb4FW3B3R@0>~ zQ@`^q_&ZPq*}ZW!SlX|*osv?0mYcR$Zfq?LON`qL2!@pa%ffQV8v{_jq|6bsbLus_ zSc|b5b@xeZ>#R9Qv#6u7bskbl@R;pMu7x(YKHE?Zcw=|^Wq4$g30en!P!N`A(s%G> zl~=Qn0nxAs+ZDZ&igHzKJe-TS@&)YMW#^c^G3BGE^K81cB%x3@T%$6yJm~r9=G_CK zP~){l=$GNTRbXWkHwr8u4TJIw$&17emIi&JscyO?Z;LCx7Ntp{@*3;n84Jtb9A)7J zO*6u;WdyZEIlbw(zonRjye3^sX@T#hE|}>cXw@%kUfR-_AY3bB=#HB4Z8gKcJv+!f zVNw-*+utn~e?hKBxwX4Ba@Fn)ymY1@Q+BWQ&Db-gR3gN+3^!;<7N<uN#j@`a?VN;w z;3#*6uDg3J>$F*{#3|K9I=kNKMxd*r&~`0$S-pCb0GPI7C$7Aae;kIX=%TSN$wI9c z0eO;W)M+trx{WCGNIAo~l@-fvHSa<JMY2_$e+Z>-P9HwSLj8hX?8Fsf0Vl_%kF<Ie zgnH8-PEZ?x`X1dXcg<9vp0f*fYClL{wNq~x5b8ko-5T}_T#rBh16vu3^7hD3olxTg zh(N;2em-dLEum@7;xN2-Wa&ZZ%@k5-wr5!OtG#l>Wjx32#*yoCq?*N9`GDmdyA2s7 z>gu!dps>?zK=y1okX>nlDMetNC8A>qWxxlis7e6>;ZN@d>RSy)#8hG+=-%*sEfJ9* z+o%+*N6jAcE`^0osX@nayyrk1G)CnpPgaJ849JZ8g5&O4Ue{fZ(;rsx0lXb88C`6I z7+iJow^gcGj>CH%_o{~>t+nT6sy%zf(kKM}ySx?%Gkn|GA)p%iCZMg8#Z(QntluL$ zVd!t@#sao}z=>{6=Hj2P7PxHyM1$(S0LAOrKCJ;L9w&PVp!kr>{ZF$s&SiCVyrlJR zSkG=j104U(P%Sn*kk<v~l3)f{y8;4B{`{3H`2N|&oaE07b1a%KGA**F=4{{lReTmT zH8j%aIJW)l_NJes2A)pA`Xu|uyqopa0r>C1SogPZFo?IZvZ)B+PJ=O@OV@KMW9ynE z^>sI|F2M(&N<O2Jov6=6Al5&%E{>pm#S3x{`3uv&(tyi~?Dm1o(oD2a2g{`MH@TJ% zqy$;bh|>#m%&LgLA6aG>?Ta#%f_bJ^RrQT^0>v?09SA|PbO56DXot_o34XC-?6tGH zCU2-jqp;3E^4NJKjK0yI=~ZxKS7xy(=G;g-+$7jB*6uYckAV8_-}vnIn%3^rYM*DH zO#k-UNjQWQqUA@lxTA<)^6NFC%pt9Yc^QAkE8MtCuRcpd>$k92JCK;lG0FG3;`3A# z3cG<`i*et{h*IwqbQ=#k9^6oV`NpnyaoGQq!>x8`R4DCyiX=W!IL~z}a-yhBUA8eb zS4X=hYtCoF4l%+7pB<ZVsXk^;&jO6T8vRY*Dg<5zY%4Z<_U!oW0iW&5V-lmkFLB$9 zgkurTyk!j5Pv{;hL}E(9Hr2X0Q(BXig_f!X?nr)2*Kr^=hu9UBziCVX3_mkBvXH!9 z*-w-?AFk==X|GW=DO=@1f%ztXntXU~I{V&eHo}#C8nFD&z6dkr&eQ$p8~0kTzA@-l zNw^?aZ!$i{Gou)=L02MIB6t_!ddBGVVT;3{M2v*Jr|FN^AH!H1ia_QoSTl=(ieR7i zG+pfi4er;55k<Co`Kw0g-+yrzb+YqRguI%MahWZAuOUN5Iv@sWd!TyHN?vLiD$XWY z(=*y8Ys{%!7L02BV*)4zEJzqp_2nV$g{H=(L+q+)OiCY{AO>?;-XeLR23&7@MK|}g z2I1}quKrmpmQ-?`CE{b^6x+&FRN1hPC6cbxd7^qE)epMg>GR~SbRh+w5uxLaEev1& zLG#L%MAUpm83Si=_K$e~$W=M`&6L5Ug+6(s5Q!1dfRx4m!UX~V2GqCzStxZ6)vzkD z-}5U~$mf&G;vXDwMMEekxPZ5bT;v82-veUrV~LwFggl>{J2p38kHCQXu55yj@Pj%u z+_VR2@YajA&b42uwCX)QyDeqNXFJXGkaMrb;nFEU!eCsqM&@GS?dxjmaq2#Lp@tmE z2lop1jfMnW#7wK2%Q9l&v*lJnN_sLmcE&~xVK`1fokF-zfAxp=t~^T%K>n^}BlH5x z7nL=X3l=zL`+_^krI>+p?=WQB4_b=hW|z5?L(dPbQnah*{;>Vj(2|^vb6I2UFKOKx z-5sX~CzdF=jc*d#m0}+Umb$*s6uR-0@Rt=toKqDzQsbMMHg>m1M6U6^VK2xlK+~R< zAL!+<&D7=?S=X4pj9Z6iRhcvf+L`G}N4{?p*9Txw^xt40{y)OtpP+yTL6SAO&cu?9 zTX2e}b~|JR-GQw&wm^=-d2`<3+7Qf(I8(F~6bxf&XB2Q_sQ$FO4Er@-Xb+>MKE2IX z*AXN;<64u&JbM?*E-NZ~Ju4$*bMn_q)HRd-SWm$+U!PAl*X%tFcv-VuE`PHC@*uhC zy<es>t2RYzdx5h=<rQMn^?gI$B8$aJ5r-2~Pwci#<8ji4j1_gc*xs7OK7_fbUGt}T zwmRos+F9ep#kIQbetC?qTB*6BOx1w;;w#8C#O<)WO=NhJ_l~7=uMX{z*7dOX3#vRI zakFW~N#-ev?By79z&lJ$F?|lY{iIWI9J|Y$P%|zhwfnA@uUMxzh|k}FGqYD?j*$SF zUE`QEFhm!S^tUQuH<x3bFqRJGJdAop4@`#Ea$VHXg5P!nD>)PPiPDz4kcp`>Tc9*| z(J9;h4&AaVitaVAFUkz7OSTsKMip7QT?Li-v32ofzAT(S6^|RGxahM+L)XQ6R82;` z*tj;|C$PWuCF^}CY&12#U3_R4ou?tu;`GCG@tm4|sX{B(bpF)qE7d`k>SDigPBM=F zvZv1ybR~W>Ye*?)$vZkROZl|&jL{`>b6m67ojpfW)3?PG5!h>tx&F9otR_2J=+Vs~ zwX^3WE<Ey!G!4YxB+2lbui%NdZJu3+-uT8ENcr19(BMw@LPL@~X<J;4EE_j2D&uy! zu6}onw~1ft;}FbHS*A>7I<nOi*LySvJfl)T3}p6|cN~Sr_zNpW>UG)W#mSnlU*G4V zOKD>vUB+XYa^(b?cik10{9=xK3wdo&7hi+8MKkE2KBA%HkpkWLQu|~ZWxk|7Mu1Ro z!__*-i`C8(+M-=f13l|;lmM!5R}}a)=r)2w4rHeK-LPq`{~iXODdG5UFwg^Fpe^{w zuFatHP0z5L{=RzMk>2~f*<?KPc5939o4wC$u|T!zB~f12+9AqvM7oyY{k?l@U5Vb( z9LWZH*!X()8fA^tTS+d8-CgzXfQKC=(A4H&J+Z7u#>S0dX#Qk0L0Q4665*M6kJwMd zdWSY3PV{qjoTgAOD}f-(i<xZb(}ekyd(l^tKe0RR7FnfXXu^XT4b=u0$vqaVi}(Wq zx1Tr#d!7fGmo;@YTp7{s4)Em>@ie*DaU<<bJ<u?j5le?!A~v*IwVvHDIFjslniJM~ zk!h`v5yX4q<jZs8C^N4BV>~r3J}vXgDUzgoH(=;UK@qoAh0-ho{et*JyHK2+p{64$ zwFMkM%SXw-nuE*=&qQbimJikrIN|QlcHH!y3zDhG^w&D+)1K+@tMjTV9+9%)uvYb4 ziyRhG_QgugC;M9G?TQ%dd908r>|-ew2@5=Gns(*Ilrx-V#%>RA$IRy|RlYojs*nP- zy+&czxlhbh&I%_w#o;fo@WsbZi6x|X0KC}8iK}UHSzY@M5Mmj0e(cPx(DzpyY=&=X zi*qMxEnL`he?R1EalIqVLJG!dnb@C?n&KMV0Ademl<8~GZ<hb-h^Tc}Jf3N$cfQGr z3aA39AOMBB6IFc?2;o+0r+FU4Z)<cjlhVk-Dc>eykCyTs8Ap{Yq&kcRZ&(ay`F^RG z>zx<QL;8B6L!M2Y!wNBR)cCr5p+W2=by?z``BCA!mmk*S_#%gC%I4NyuxNjHh&Qbj zSmJYK5PVgP1f0QYh^E=D!KCd}_xjtk!w5unf45A|1-V9PnLW*exFyaXL*z<?s`vL= z0VT(CRr_a5?rUm#v<`JCTMTKp2ghoZ3klifUE}_=x&oU<K=qf>UG(^qmRqAb$)xU% z89#zceQk!d%*Hi|?7byrogz`cv1+3I&{psLz3vK)crZj(Ff;GqRm|V!fcQ2EN$e&% z8|g2sFti{j0!!>zRwgNM3BQ2VxIUOPDmkC+2VHQA>&np8z%Povov<vds2*RdgEzR` zDAv&JBvcgR+V(Nhv=K6m%=o*u`9eEa1&W*D5Bk-P0cYWhW3G*|kaE?&<z>F(JsU>> zjGb%5YyS=n+Ww{u7GcThds#X%`J;(OehT9r&|?Vy-t<J3RlE7RSnzDh4FZ?fNR+qQ zC)ef5OlN(GxsJf9Ddn3vPph6td;cZIaCeeo@6vI8e!cCIeKrnq{c(#1uUQI9k(%1g zYW<tl-$IBxnKK(?`&60E=b`*I6>W7E(yc(#9?RHNqc#8%Ky+D8ZKu3C1V`yxtX5Gl zT_tnrxvMFUk=&Ren6bS2rYtEt*Fd{EYtCfCjwpsV8ZU}M6H1**tnUnGX!SG!+X*dR zGgXsQ;^|4*30uyqkW-XM9S}i$unfHOx&e2QA>UA5s?M-uo#(+4OpbYv9E;U1Wsi2p zu2k*ny<nyG@BHOA>Gb7>pxhq^D_O<CxHyF<_V?P3LcE6EQOCB1gQv#(X8T)B8;+rm z7OJ+8MZhUFQV*YS#@iTAHKi?N<ClFla>7rzw8^0vh`06e@uxY}O(LR7YC*K;>QHsH zpkGZ``-6PB(S+0{Hxi^066jbQ<nSP06H~*H+-Df9tA13pR=z&x|M}y|yENj#V82FF zN*2UBIaOB6&Nq9Niw@OhW>HfLD=~RypeVCl)oK3++0)ems|<gaVNGL`3+yT2za*R3 zD2q7dg=B0>Hx9>Gs^QqB(QNV}NAjCM^mvoK*LMQ{=^4fRJPT&HlS>Wx__`z$1K<Hq zOWbW{ghP2^uiIQksd#oLQQ{iD#R|^SyJPxNH(0l3mB!X{&Y#d`p2Ox~LYL0Y%t9~q zf0PUzAiE>XCb_uUzET<U?YbHYgSRBun~U)Z*RF=;d4?4Nk%GmQ1(xt_zKU8Uj?G)U zaZErr>9Zt@0{hC;-US5Q=BH}bOWyd#cq2-WbtP2VTv{o~M7grmFrR3F>~<1p;XgJW z5~P?WHDiGI5j}#j^x2eZ^o`vWF18@J$oq#VAnd#NAazSQjjgRIQ_m&0>05vI|H%`U z-a&A0oLrhK$j;@E<k9cQtD_+o56RQG3z)J<=iZTm+~7}LlA8pAO7%{V&G<ly3mC{M zEFFuS8bA7A@85Y@&6Re<9M<fw{eiM3TM(53n98Wx&qH3b-0{Ur!KAhI^_1<Co>nQu z_SoUwYQS0G_3$y9ip3t+KhB^RDC?_;r`7KRR`ctpVCf19cy(#rnixPnL|PNXHyX+N zgGOcn8krYFOJ(zba~R|CH7P0YSHBQEp$<L)f6$2au3p{<G)c$AR9z&6-VLkU?u8A6 zJN=Bm^;kT^dh&Fu1%bNL01^Pnyh5U&4Iqwl?@>_=ARg)vo=~d0(x|cq;AVyyhaH#q zx|nl1lQqm3Dc_+~zl(36Y{~dg<4#Cx3+q{btfP-Z)3aq#%E#9t8j|M9oM(!~u;UOy z;`*=d(x}ZWu}}67lniFtaC}Vc7~MP8#oL7~I|GT*dnKl~%yk&t6;J44=U(*(i5(RV zxYl-|mxQmSYDd(93m+pIz|DBc7q*NcB~zU`)malWU-HL%gV1#!2PwlwL$F-e3w(2v zJCr281S~?Pso{7b_$$?|^<I^;XT%_sWhqn&1wU?DoGaG&IQH1&F`&nKc^a5%4Pe+p z<UB7I2Q-qbq+dAubPc-LT~$t<_f3lj92ak1T95lyH8Y8kHuO&LF*&0Gaxd@Mv0Hep zoK-op@*2?3NFrI~Nsq6KarsaBr6zetg_SMp-M&s5+CJlY{+5zmXXvJo-IBXssT>f3 z{iuW{5X&{VkpwUL!5wYG|HImQ$2FC$ZNn(`h6pG{V5ABNNbfL8lWqv1gr-tM2)#%% zGb%_63<QXD=_DZ_C7}jJl->jqkX}Oxy?1=$ocEkF^FGg<w|pOe@Js&LYv1>EWv#XM zy6@}CIyZP=x6fuwT60Wu0K?R_)V};^@nLF5wb#<#!}UiG5_yf>#onqps{NXQ*PH!_ zHQLRb4nERM8hoCk7m^XFsOwm571x|DNu-9*UF#|^WX{eq;6rH;o_tQD7#n&AxMM?% zl7ssFM0J>-+QNPtD7zyv;~I`Zfb@m4Hr0&Bh=(Lv;YL|Q!uG74X@AkG;Jk=&bl&uI z>OqA)+_<4Oi%S>ez%rTJcf>aDEW4)(UgD{;+@|ocJXFe1U?q2$4Wt|Co<TW-zv811 zm+lu##vet2ibbs2bSp+P@s)dVbNnNs^vWd}Fma9fR!UGB&I*H}nAL5+)ojWm_URI3 z9{98yaZ5T|n-My)<nHc#hOR1CQY|YdzzJ}*U9-*1SG^8+bLYMZr>vOx<hvzLW(<>U zc9hibXB7-v>Scu9Z`v=+J^f+^KBP`7C(?S_J`o0oyMTWwPZ`4gz)e0o?7%e)|68>! zhl73&2zVvG>@N*d&tKt$eJ2;mkqNerGz?RG;e!7Ju|0pt`NAGV$$u<L(B<7@=OqUd z(7=g|`|IpG-r6GfG`!6QA_LD#JkK}(gw~2?vZdJmiWJq_gfxTf`)LOv$3H*3*8MP6 zL2P^>fL3X<sAo66Cz3|VcLSLbgQ9j6zQVz@bBfdhhM%R*FD4oJYjKw~(95{M<)ZQ| zUiBXvBFg$*%lh)J=ZCTD`gm3IgoY1Lf(9@7{~{%M6Cq#t=($+ymmhEg1Kev>VdI}1 z8no}qVXbALJYLm^4Cz1_vnI*l2Gaba6X)TE3+}Tj60AF}Fla{xPQGowD*tr)M5!Z1 zVz$6o=cPdJ`uz&om`aeQ^&`=I9BJofZHj{3uZ43ZIOd}_JQR*V*`=gtX0uZMz1!45 zto0TMiQFZeu5({N0e+{o1g~9-93Y8#U?1sm38~ek$g$uy7rI(hua{J9siC{y9QNX0 zU7KhPsddO7a>&!=Fu2B?ut4$Byvm4c-z&bkd@Uc5h_9<&=#RFI)Cp<wbz2o_Ny6#q zyp;WRBk@OIE&nAje=QrdAxriYKp@kp2L7NQt!NC)ABNTpWjOsivoyt@>p3_w!0agm z>#L$6HWy<)>7qb;0jqkzTH5dWfs~^qRI*v}ds|YLKi`aS8H9GmcKXx3xRmD^Ue?5~ zTTc%1`jT3Rt7Zxv`lFbVz`Z+E?+>RHFxxkBn_Ry*UhRL7u^O%B@Cr_=J8{3GTXs^g zpU&~G7^VLl;~Jf#{XKTir>`5W<02H=bJOxShqhLCuZo_^PZ7b3SPQ1aPS=f`UQE3Z za_&`uBEymvd0z^CZ{bo4)(&c8wF7B3YHE_(V`E-(b8d-G*WsT`eX{QZ=FqmpWW$R; z^qW!x!>zmSmi|uqLCcW!Yt>rgh#E@Hd6s2bN-=oqU@+N!_5{~Z7po%hgONV}Vq}2} z2D4Y<xflMoAZGq&5aEA|V*DQ{PK?v%`G2%<-)Ol`9W(&9rWM;cA-E8r4gK_96b2aT zTxFtDFti#aO*KqNnEby-k@c@ILU%H!X%3i>uwc}BBRwm;;bD#7l48s81jAgWy`$h) zQSMU`+~L&)vwk<!>1@60;#W)90LCX5{}l-MJ+k--dfL@h(`M&0;N~iwqrM%hrk2QM z)=gyU9J0gdF~zZb+`P5qHb!wytF9BKe?5`CzCxR4^58q&pN0vJk`Wc9c4XwqS59B= zb6Bu8%i}UP$Xj;ZN7Xdav$J!X+nebkgs(DZXA`5&vxt}!Z5bOJjsF}Rf!b)K9l%+= zz+C}t%)iKsWzHNI8pLX=x*0fqn%w99(-`2_BW`$L9)I!cl$diMFHIvRJ4Z932T4OA z3}`qI%H|DxZS*Z(80`(lP|&i2cmh4ntK4GkfxTH2h?8c~=*<D7chD-qF}7AI@roHT zV((loDi}(dQUIbDrMb92{&}#pKW-b&Z6C#0ijQi~yL*Z`zS_Mxr*|W_bnV%_xCA@y zD+WwC{6TrBCqp)SnCfodbGjJpIHs;(ty3)Gu)Fj-o!sYxf+4nu?W@|~>G0e`w!bEB zkI;HptHo1~4b#sP##{<?^{mg!+`WXEjm!{e{sLOFl3~0tdBMI};;qSxk8!a=a9vot zk!ZS7VoFbd9Zwy&a4DG``+oR6hp<eDbByMvt<&c|MgzXF2ld=ts%^Jjz?Ni`ims=7 zQ*H-o6-yH_5$M~MjYaC^N~d4f@bv-P&@{{RXulbNMoVdkbKz*#?$44nCXlD*c`Pi; z=AtB=ae0@H>`4h<u!Xc?bmA&%w~_u9+$t2zjGgIj`o~=7OYQARMOv)CROSA2paUNW zBRG>q9q5ProL^b2;(%Yi4JG~&(DQ_Te3q6KX%e~17m2YN#8kT&;8$S3ELjx|1a+{n zOp~>(TFF+;B$}Krc1goa>_%$`@~ul#BUEVDp~}z%PU*V%Zdvu>pGT7s`>oGMhLPM0 z2{KUL5SR99(q!Ul+R4uGdN;)L#TE8q6RoX=X*j!8R~`Qu{UGSug)_-o66?9WijrnE z7Cwl?wLomz{8J4KhDgP>zdCMQ$Shjo-U?Pr35k23`Ev#Q2yl|P&t_{-V|xUTKPWGF z1giw2`{D;58EBssuI4F-C@p0P8oaKdUUeo}BKkQCYZ0GayP<mu4K2kb2f|!)i&teo z2p^S1c$vy069P%F;#5mE5Z$SCIliIR8uD7SdmN$NB`<J-zH`q8C)06Rnz(%6bLeek ztlyqudA)|s&Z_LmqWK^VXLw<<`a*Q%VqYkKq^I8KM}Q)-a7%Wg?qc^VRKKK3pH2ZR z#yb(z4Sqrfg+RX^^77=d`@*L+)dJfdVij$2Q}(20j@u#bxLUJG&r|h<J^;XSO4sEI zt)xp6^kfRbI$9DqrFi#^P$*H@D1}ky=$1w|5gf&Ygbx4iF=#0YuauQlN~}-86<OiS zzYH;Am-MqI=h(cSMh`wn0%$ngS9PxY%LwtAh7hVwGEnb%rTPPCk`AW`J8fNMVY(mk z_yJb$WnH0oyjX3@%JZ({Z<p-tZ!^T7i09h}<suD7?$TuG(kkLGn8U%Wqb;GldcunB zuke-whFmG`g`~CbbZ%;V_94PG$9r2&;P}YMi7UXT#_9&QgfZIJoSSKzh09-;e{tAv zdgRsg{I-LjV6t*K&f1kQ`t94Q>7fgu)N7!J$(}X?NJhOW_~+$+TwN1A(pz{?aBc$6 zGb<+CZ}kzWH{O$OR3N>>*NH}bYo=#}tA4m(W|$!K02paQc^yGY*G?v!%m!C3hDh;_ z@um2En^yo?QF0L=vjHMQwuVWLVaf56f;9^P7AHLt(q{e*CHVgyf+R0kkmZ@rKO%tt zHzB-tXnQPWRc4{dnDF5sd%Y+2-yz-54N)jS3pf~vhK*e-Ew<qEDT1cdSD5H$?OZCu ziB4~RsDDzk3dO&Z+j_=&;eU@o3;h2XgQU&wbQodG3u@u{As++1BdRkE?WJ+3K)omh zYA83kc;pgn5_hUjjdL^I&OgXwo~UMNCYp<xx8<!V-W)30?vr&10>SSZ%Xke+UcduF zBE$VJXg)2t0=;Rz$K39f+L`gQueE&{v3(wVKe4?U1om?<TjR`%=nplr5a3l%C%;tn zuQD|n59IzdbZ~>BA|t~Wf)G{qXpYJ-|Ax>%esV94-DGl$HE&%2<RlXzd-~IP0~Ssw zc)0zE6Vyyp$4;9^yd2KUW|f^=%AWvaldmgyGanRce=?tS%$fO{Cs_31lc5jzK5bqz zQSYBzgvCn%H#TmQujo)TgX1i5faTd!+JfOz5xi&dpxcW$5XtXV3^Gkm1|2o-U1>Y& zQr}lnyEnq-w6I-|IpA5##O`EvP8Q@B<i?if6~wUidUtRzqT!hlH$1M@#78!Y5=J`Y zU7QP-zykxu>Pp~h7d>_b#*k#i(<uwJfI|31r&%R?fUxx3*8TjGCQq!f2qVC)t- ze-4py)tb-ik342)AF<qAqjLbfeOAiyI+?boMz(PZT0AK6s{Ydrwm65U%bV=P+c(3# z-eJ~#LK@3nZGAnx(bMWLw1`IYE<+$QVbWHc&0k!crXO7k?<=_YzDfL7NvuMV4uQ_3 z7chXw73GHc;k`&n(q24Ak`jsS!g{wXG=-|_@`KkrmNV<N)!vkE-Vo8uzsw<hyXMKP z3#Hipx~Ar?@V#-K;Vd2H!jgCqUyKSoEwhuJt|KpJzNXF5JF&Y0AdI1aAVZ0^b>bdH znRTQem*Ff;dKIfjEr}O9IBc*u?!FS}5@>$J4t_a>xT9QJ%AUOc+dN$d`hF452&}Fg z2h`LyA0U*)v-M(=a^rI1?hGMIR#}jH8SmEpUa2p{X%zOSc4nvUu{7hJz>BmDf(@fh zqx5gdI*a<mwURrmTG=C`mQB<NlETrv=fjI=T+G-EIMZAqP1^`)pQV)rFMpk76BD+6 zYV5htUGkphLJ{!k6n5i_r#4dn52DB&jee_k;qhC$nn&$M*fV<9q<HvxB{d9J9kXZF zertoMBwwEK@eOW#cht?W1$TZ`785VWJEV8jD!n~g!$gS>I5%0#;@v@eloq+Kh*hVp zWfdO6C4sxnY7etcx858N-+gm`%VS;hmDejl=0A<8@Uvt^X_=6_(#O9JnfB_r`pNvo zg9?R9d5z+J5y1E%sn7(UxN5F;b1-~%)#L?F)(~AA0I(1atIzciy?zUW>HQtka3rm8 zNgnO1ftKHPy7(WXG#>iM{PD9Sj{y^(#_F7lnCii`B;?R<8FJcYo%i8&w88_~cT4vd zdA(E;KH}R(3%_WpmuafHT`e_JhG%+QqyK-4Q}Eiq#?+R%!yP=cb%lFPUac!@m<M%< zILvRTXWw1^*HPcw>qf?EsJmI4pP5T2tk!id<EmE<mk2{rnV?x;;xs1SZ~&*(6X-AV zf0d<eE#iMheM~*e??~V1?!SE7cJn>Fub@`dFT0;G2Bg`A4wWgU_qawz!-XkIH7Tn6 z{aBY_l`cfR0`)P#V#uNB!H!sON`bLtNODZoE1D?8>ICJ%vAPI1x8A~$SnTUSsFVOy zm@5_z5tO7bT^vCvPM+mHf0m6?;Th^t{W(?C$IIu0QB2+EQs89w`|!Sbm|9}G_(@P# zzKqjfWDFs{JiMJW-Hrl!vRHpU@{z9C>;;(ugD{oM-c}VG)l?$N@%<d7rExKn=@|p1 zIICn7!zHt$AEbF#jGQ>wfncG}v$OhEYd${M@h(uPwJE4<*2K^XBkoD{y|J@l(YOnz zTEa$3i(WZ9FR^j@Y6`awM+T;tshFR(wGSxDRmOeOplHwxVNFYHbO*y7bPUF{421BV z@r~BVg^;Vo2i$puBz(*p(?mriA=F4lsEveHmQ&DFbrmeL%uCqSk%eoA_p2)T_xSjs zEFkO#^jH^Blf;T!B#=6@r-FdD16)HNJ4`W5SEPr^sNL~*mg!zw1rH8aj1CJg+1f?e zzm|tktJ(OKlJz6Y1Vz}76}5_Kl9Xn}eSzfrqwNC^kqLn`xqh~8!`l;Wi96OeVu+^H zXrTe4cu6EMA)z|J80t@69&a7p$m}x69UGVNAC95q`rIfEQD4YtuURUBcfG#&dkYUi z-q^-UE*VVGaPbxw-qFfVrVktE-^(;yb8!_r2I<+#fYcY*MD<wl(!S2)&ixgRx1Nkg zM3+b&x_XI8r83J$hC<XICfLb^3J`k2ykIi6ykJ7I?otw!W&^n~pMKw<CX%&BP9$=r zo=djH4>|stEjL)#Q@j?wm%i}s-OB4iu<$}bOnf%x{`+zgSs|_Gg{p>W3p32bOo8!^ zsMma=;_}+s;^H=0H=?eNuZD2M8$PAVM4UbvvKVTA_%teQUM~DG<C2iF8aG3^sM{pN ze5YkYQ^|FXWal!soxTDS3z1aj9f>L7nGmTAT+nnfYpi|rXCe!Hp-MkHJ3C`3>V}}4 z@Eh;@1O_|qZ{u^m(jZ??twEA%zK^!H(XEEBb^%;13g79b>_oL{<*(XSnm8EeI<RTd ztN;!|B-TckxZ@4L`B{(q`iR06*vB9i=e7g<rRN51Ga`xY3PU?KqizA*-9&fv{?L>Q zRO{m)`xxp-)7K2{#0LZgtVrdVVrwre3=cMANM6~c02P+T?N9Hk-cNHIONAM5*Kq(s z3QKzm-AJXxl^6wX4o)NEvft}gOtu}Ot`ufgEcWVz)#}CPpf77ZweW-nzr?kRDJN2| zOn1wgNn?9!QVXtpM0x-%Ivo}$Dyim1@{WUX$4gP5jV^fAk#235>wsb9Vh887_-tSo zr;4VJ&%p{c0MMX8ZK2%789f5vc4l#L6Dm{VtL!^RLNeEWTO#suIA&M(KVsJdvCBBv z-EDH`A+}vBr7%i$lGijr`7uP@3X<|D($g!(cst%=1RfV;ylFs_9noD{Ob}%EwbLzm zkb6wZgYz@bFKinWySmkJamn3xEbx%|awBiFiyw=w6gRG!@#@Dj4hD=a+p$y_YY`uQ z(J-;g3dGE)rUWr?G>)ITH7~ds5(ZORz@{ygho)g=YbqC?Gw?kKB!2yzRGtiolQj!| zFvqvn=EpupeA1f$JROu@Bq&xk*qvQ!3oCK5gd699J!~I6oqLreKn;w0my<+ENb;<; zkozTezBqMERb0ND>_3bvB;Q=L<WDx={dp)p<$lV%QW>I8CN|!}3IVbm-nKz#YDnI> zY^A>%GUHR6%0S!!kZm9X`NE25o*0D@kRf)szoA^iHKLU!DcT%zo;vI0HIh~Gw8Z!! zd^SFMoL7M0KIcfS*YUL$ZYTPCYWd<O3rwT!$8<fR3Q*RUmUXJBMaym$lm71lrA=EP zR0E@8Tkx>(@W5Bixr7t!5y);lSwEkNv9{!kvvp;Jo5&8=6xE>StN-B=<%dVNrhA0Y z2Qz^7T`HEB1&naX;pphzY2-}|T8E>XzN{usJNA?htCj@ra0pf&%w(lZsOW=OT&o`> z#@ZLh%VI==$H1g7n225l-~|>p-uX(j@o-u>3%Nv7dB#68q7RaxIaXW(ffU>H0<ioi zX^vzI&87|sLj}uXD}40)tK`#I=P?V&V4x|vs7OR|q=dPsDBmKv^|WgdnlR`7`iP+j zT0p-G8zroSsJKm1bva@(N-8a9`$!+Xb`wlmX<{z|ye<^Li)Nbrz-uC~<we&?Vu001 zV@1C~aqP(+mG~~+Q!7zVeL&3TC@>Cv#S5Y<ijTckQ5lOBCE9N!R$rr+L|j68vJ0bd zMl@Yu?X8Z;l<Q8om%o1)fyotNm+M!IpZ0&51L=Ny;r*UsCc=uQuB&C`OA^-)Diz=O zF34rf7}(VOS9LB7dAR_tC7rc{44QG951QSISb3=jD1m#Wx*?u?W;AZnj?wBLxr{qJ zLfG!TSt}U2_?@o1=-ly)e-WybM1NOU>8(v!KWv%9hiRhN5*|=8?Iug!CooT8bVY%% zVskA{&$P|Fj3{)NJPJmp=YiW0r#KdOAHlb<YVzZo55}AaP>I$qXvz`zDEZ^py4;P6 zH_Tg%KBlqA69yoSM*gU}><QE0k@NLI29YCBOyO2U;o`l5c$)?dZ&rkv?-CiPq7VEs z-Ud}!F13IKN?9FucEPam*^mH=yX&A-BFDCj<^nM=Il<94$i<&oEd5FbmR4P6Xs0(& zydCnGJ&$(OY6+=G=`c`9f$W4JZtlHxFBm%iBT8D3sE;IiP6pLD)?F}+_{QyZ&`yW@ z8tUjPJ=^)v{nQ~*ln0rfTh2!1Pcg0EcB_EuwHM<}HcxONo!^{oj9<Qa_rR{;8%;dX z>)$c?EdP<fvl4PBgWI&kI2ub~M}Sqi3nn|ZM^tZi8@zuG^(I-A9rR+vAa?)^S?;%< zu+upC_fTX1F)K#7S}IAY?$1U(WSn@79CbA?c@j#RJ{DXz2Dqfcyc%gGP4kHlwypI= zz6`W2si5e;(sP=n9IRcOFe+pCPM2<R(7bc4+&2eo1M<ZbpjM!(j-MZIB1_gO(LP6) zKLKOFUGh4sn|leC)SlF5l|7JBOPDW+%TtDvPkK{ta3RKy5wl7R#5kcN_WyuhT81&* zW;ng+&f@~iNkiS7G|^>Ei*ye?ERAU*r0Nt^^bh4{ntki#eyMsG@*Dh^+S4uPg(|#7 z&ndJ>*YanQRsNG?=yp+Hht<bu#Tf;Pp2{8IbrhnPo<||SE~rtifBE%yx_Q%Ap2W%j z!m>Qif3b{G)c-bPO*}xezG%T51!Jbad;Ud+z^)$3orxwc)S2^t1j?WA|NkEp`TspA zU+yZ{ajG_Ym`smY6kJvrx7myevDo^9yk{kh{@|CiBOsZ)f*v*Y5e_Ul*-=1<Th#XM zTyjZ52$#b0`=FxIyO83w(rm7E#P-oo4F}}RH23o;_g;F=`yP&eOQZ}i<?Zu9wjN=N zg_`%Mo6PjQ@-j=~jP;rJcW8~^<y(j~o~{7|Bmgvk$*{$jRHt6Q+HQ2y<9|{p3g(!` zw-*r9F1q9N_z$FkRW7G>(+r*!-|1Kt{T)`w(%1XV#HCkeLWC6^R#+*1eBD6Iy^0GU z99ij|Jh11(flV+Vd(ektj9qq6E=KJj#ihuD_DEx}GxU6=*WUiDWOp#q)1L(u*-g)h zf>oZqi28~795V{gO>g?IuNU<)nao|@ONS&5rleR1vktLw^WF`w_KyX!Z*GQVx+`Im zy&5kQj&D(Sg`=&8qXRQtFQ$DRo<ms~lR?H`J9HDu_-I}GoN4*qU$;?KPtUfY9*>-( zDfpKEMMbfqx#%y4f)DvlgJE{ywfiS4AVyH7WnVt9I(XRRJDs_5{zUm0+p&?_PV?+A z;UeTgYOLzYh&0L$E9{U0apQs;uR67pU89B+H6tWrOcutI7k^oGXE@TX^WCNu_4e5; zkw&I`;LJT+hnQDSMpqs5)p;2M4>t?Mff`kbp-_{VE`@5bs%A6mnw7~Qd_f|uXYMv+ zco|LFGTrUX;Sb+ZxZmX{l6gZReyrkf8Pbx_vJ0V|<wne(;;(-jzch5aW63$kMW(24 zPXNzU%_zjz$gSip4<8nt!ev#t$;R*5WRF~hf5?Mpl7YLP)n4du`xoN2EbW=v0dY)D z_&z}|2sU%q;@YsEjOdcJl}?d~9+#3phMz)Ew@~?j^vcC2S!}{d@hPbguLcF<8_HEk z_shx@aYqBzN$!x{L6;da*xkb&U}5bSpHduO<~&-eGlXC9aih6SffV8_Gonzw-Hh-+ z*^!gmv?9d~H*z35+gwGHe{PY(V+r%({H!o(pYCoJoL5u7yp;+%x|pHl_qpbkm>p*W zTkWbqSmIh%bstVt->aT&axUVyb%?c)<`>yvCfd$3b3bfx#pd&hX~$>duDZUDBl9>7 z-IQwn;GJ99cD)vSAQN{D8`G)krD%AHie%&FVc$5Dr^$<h+AY`daV|m3M$c`JwJV;t zOK4M}g^LXO0F_}YnUKX`puFV+7%HfNJXbCWgv*N2Oj}|$e=&6-29^Vd2X;W_R8(`Z zR=;U~>o<zNe`%DY6Pt;af<(fn6G7$MR97*wyM{?y71)^lFjCs202^cW2@YT~Q;pPN z&q*YL0<4qfizR&&@u7O(9-FWO-zFpl!lqx&BgaL`rczTein;tE&=g>fs=jxg*hV+g zYH7y7%DcDz9I>2gj)Mf;)ruOMOb@<l0&9GJ%IpH~7TVsl>_W)p(WMv=$S|TSX4~r7 z$wG-=%f^&JRShBDw~TBG?;B&2l(Fs7<5zqnGMpj8(PyOH@YM*+5rH2STh8wp13q;g z<L~f?a&m|)3EQJnWJ}<Jidb_@vOc~L6Nyd}qE?DH?=JbTFb>Hv=mV|MlW+)q+I*Ar zjSX`O0=zqaIYF~=1|#&w)}Voy(`AzzJRP54Y!lhyIwP*IyA4-N7eQ2Mhv8YY^(rtc z!hm%4E#&DyL0U_0PUjStjTK+DSMFAgR*q-y7dO%puBdV$<RW@ee1|-EAL`&MtYBeq z4hA<<06g6BP6;1B_IBcwqU>RHtSiirN3aK|R{OBh;V{L)&w`o={I!x~gvq*%4oAYe zRjN|XzUb5hTyy*biJ6_Bj87=rB`tXtC1uN}rFRTfkH)mTld=wN+{@RGqPaDGs0{pk zBY9Mbp=Mw>FyFN0V!FY_2VHjG2E8BeDtHWWy$7|}tcF`=#0B^*nN@iL(*lew8ZC_c zqj#lX!Py@33@rxz(t*BtP<PuNQUXbNHm~D;9xM7jd`ie4n!Q)2hZlv)v;N9T(y(Uo zwBmVL1jM{Yw!H}@xbeVORx+cE-Ogj(w?mQYaxh#{OpBppn4Sy3Z?l$p%?01Na+iX{ zD2xS^57r%UJTJ4fD+gFTAAw1&Dv5PRkR#^tES#9;7_C=C-i;W4f4c^BDyJ-_nt)4; z^n^opmZD*mlir}06Gj<PI7mCwJQ2tK+shbckfC3}qJ#2Fp$B`baJfBMXwNmJmKsH| z&xQ*NGAhNGktZY@Ey=Lb&bSzNoSl=jbA5%sXT@E4S7J!Ro9mkHdHI31{#SZDadyC* zwD|rSMf0ucK?(M?Q|gR@o7s&ZiP}9(2wc4?MHB@^h)M#Q6wOTftM6$mw346Lzfign zgc6P<nzYIuUQq&_mnj}IY!B3xw=g=Vzc10;O!L3q<>9#(<?x|>d@fHn-jFpu(mEmI zq@J^Dp6S3WugKXiDJdb3BTpDuqQD;+pL1886$z7DR4$y#i_UDb{^e`xWvn9+46m&9 z5Lc9)(U_}=v-9y(mtsohvrF7iG1jVP>a}uGku1!IO$F(D^aH=}m#JW?+<b?Z?0p^d zeequbvcn6hOfwcXxG$|O-q~`ZnsY&n0L4_bs$gDMvAJ;yDdn9O-yL0)H_2p#YM*B@ zmWIt35SHi$bz66I)(Z!5mJQ+H_@NQkfv(!Vih+ESYEFQ#!e)<RfxmK@NXD#Tm77Ph z6_0&W+0@|x`B{Ee$DIt=LB@flkJeGdamnIvcc!<93rZOG(kmc;5%bVZxy(gRDn7(G z#3s-Q#y{y04Zp3p((NK#Hst3G3cfRT#iwT+Bw3kIT1>O_EY`x{Yck+>ixma;_;u2y z#c4|up{8uA*vB+}PjSyD(p;EYcbf0k8{ay_l@|YGPJrh1sg*DBw$Xt>r+E89>}Z#Q zDsoU(-i?3>)vmvMcMhYXlr|^>==5^%n8Szz6=UMS><)TDGF=bW>g9s`6kvMoV(Grk zyJ-emX~$hG2J(0oak!SK-esNQ0}H6ix}6Opz8Fd7;LLN~y?Vmw8xq!|W8j@1reOi$ zGp%MUlOtA}s<Ew6tk^!RTN;&eIFuRG$ho;1kHl|qAMq)!SQdWL2yysJHj)oL45 zf_`hgFy5XHHeD~Uz1wEzmg=*#th^K{RwTX?6-AWF>?9%Mndf!%`o?t%2?Qj>y`OFL zmxpXxZ;>8Y9eBp$sag(imw|*4Ur#cL`7o&*`H?{E7}4pD#5LM+0`7d-nG1JoIDT<R z(}Z{jH`%gA+ZeXrOt=-?7wx~Bo_XG5tbxgD3{aICnfDk6>OhR%f+!c1cb{~v{lr)* z|LKPb*ZxYSvXy5ldI#qg`gOQ_3GH4Jrhp?*AvvM&CH>Rh5}%E&Y>8r5Ph#ELHh8eQ zBUHoW@Gv%Le{D*{+LD{53btV~66P|KT{j>(^T;ED<(8JLmcnS{Xg(kYnwnJnaJGkF zvdM`77B7#jFsROST?t+>goq9sDzt#o?1Z-U1a8&8u^KJ8xYO#ib%em}jfMM_xW0V* z?IZ{_K13^w7w{Mw=i{sM0CF8pECsKwJnP?y@bpQm+ZycSoiTLvnM>?Kj9=b1S(UG? zKSRR#q=s0?w}CJVn-8`cv%O*;Bt5K*T9|OZwe7|;H#J$lE-OE9@_r`v@tWxxd3gn? zZR;$?j41NPP}8q8uF3g}l(tj+ZEu9-uh-&U4o)+{^;cOsbl0wT>mJFlu!!QInM^-{ z+b}ahgUNO#+mNhuiO?kxr5e_Y?{I1jd+CR`3fH-u*Fz=d!{6!TaY@d@O<J`_Iep%D z$uv2rH+!+VeZg@<PxCOA)JmW9z?<eS-nyxtb90&5#M>#$d&b|I6K7N3ynYFXoP6ee zR5d#T7xDB9g9@~*5QBtvd<gA<Q72=+<(7ToLl~Mr(=@d4pXxUADrQ`!G*W<<ms$=q z_dUv=x}BDfiatK_)$irk_a3+O3X9jCNfM6q$L73Hnxur|G#5J~(HVMLn}86E#n+k= zf33I0QghE0WQ5<ZCMl|vl2-|1j>~Jjm!*;LmrKiPMY%4L8}-!UtHLiMvnl|={r%Ms z%0><nNgLKlS}mRE47Z-dl}w}p#l&4Ugx6#jpBCV$V@GuJ3!i9zKjwms7X-f`d0A4D zH0*3UI3HYp5{HS^!rN0cO>BrP`Y`df&=WF8RJtxv32Y*)Xq>2Z;)_UP8u49p;()KD zZ%EZdLNsNEyIXd`2RbV6rrhrRVkz&Ds%>lgGJeItS?`TCL#iKACPv#XH8oM8Uq$Nc zmpU%@N@k^~X9KiHWkLrjFj7>>%q(x`df&jIwj_={u8hrQp*PkfI6BB`*6ex7+}t8U zq<-<_G%AX9du0&tNnjaw7%yPe=L{c^E;eoX){xBB_SU6*w`y_J*Gg!%csM_&5OO%? z0_)ACa9PwjOkrxpjaB&5`$w+f!Hb*CCWB_Dy(gd8rU>AXssTiC|L)jmjix7g9avon zF3cE|wFuG{78U`MY&F5@j7l-FH-q}^zg<Vyty<5W!}wn1xPJW(q!pj&B`3xc!5P{p zyyG?HF;MMM^=am5-;$LJHxedc>J}VF8KZSTG}XBt=5G+zF1Skq=7QL6I=)^wL5Ysh zempx`3J-A>oV<xFES^X~58E%}lWrkvpylHIr~Srmx5K@5H@^&M;Y%eA+szcBe|W}P zptQpj_+MlTND@s8em9f7ow!<P4JcmOn160)>=kAsRrewnIczAJdy<-z<&oP5DcWws z^TA~+oNg*ZSG%{}#Ax~g*lcg~8d-=%cy3D5Fz%ZuX$PoPII2;&gUk>(G>H37r@jqP z5fmJ%T5Y(a%<CQ&PSc0o9-|i!*)%HzVwO))k4EJKTKkMq$}=6^8w<~sovs+jg`HT* zJ+6te_}mh4!f*zI(j2GBK<k657&ar;N3x-NdVtlucb4Gk{5TZ&{xkbnLXF963`W{l zNg-aYy4cF0p~migB)X{YICI>^v_gbboug&Oy`E)kV3@z(t}Dtn-s=TT-d;9S3cWqR z(PG7sQ^%fZx<D)=1!5mVDyi2Wxn}$99@z;|(#giz`K)nGi?u{}pd7mIn*pSFj{)MU z*y6PvoMLG_%JCFGkSyew?<-lBZ9epV?vOVi$BujP)sj%}(?M9n#Mp2{fa+T6kS`f4 zi_guiYP;ThAMzgO12VLS4#`SK(8{K$-SrB<0zku2O})X}3HQ{?Ox7ZnspZa#y%XzH z<5T?5u5HmFH~ou|2~yW-sErnRv0J)st=|qduTYTTf4elsDxm1lU?x*71vo#G0o&+E zgV)bde>n|-fjZjHH_86`VZfBLufo~#oODrMPGNjHqL65!zMyA3FDa;i^FaiPDX^!v zwmi40Mv*3H_Ev3_@nopCjhu-~_Xw9?8o%IufdlV46C18Rol35w+4rN(1`lL1!lr-e zU4b;zX?k+k%dI3%f%n||+2Ptx7Tm$v*#aHIL>BW|ZB@C=0_Q=3Y;#)A^606(8$g+| z<X?rKthI%-?rniB_b$52@Pm!pJY1i|_w5+^%Zwu8;AJwH*jaz2yCf5roHAUsdK)1N zEb(DgNCf>kL;7xJx@U`|O^GlvhI&J1rkK7ua)N<NC3Zoc;oEGC^N6&h%m%S=t{R)* zB~J6&&e{(cRR4))>G^-|M=JEc7?MJ_T^9!5zQCci5<JsMmH`3oAxF=z{i5}}8<jP3 zjtSK}d@ck2(qQ%Eb;l`?;m~By>2Gul4V|JFNF|>KE6|$lhG~5|4wW$xVtGf7iO!YN z0U7`F-nrQ!AUgz83+&&mO_0O-RKzA_#n{;!y$v6=ZxgtP#0C=TBp_OKR`$qtNIp@> z*T@UooOjX2fRQeS)Qp<6KKl?A{Ec4WLin$|biZFhrOwb>At!#nhWe@K94iVlL(gpO zl9`~k=uOCM??#;x=SV4keI<ea=_bu<>o|ywm-BA4wnQt=+bUU8b4^qIw_jCm;6$&p zT(ue&mKW;nK=A=`7|llBq}ktUd2E%IVZZm&HM(4s+6=uA%KfKvw^1=NXBbd-X(OJ! z21RkJ)Bki<AOpoQOD_QKnPD^UiFFM!ARujlS;jeiqog#xytcH1$|Y~RtzhrUh^3z& z-s#f0*-F2^H%lcC1D|Y|%u{O-GFRP_hL3#Dkr}ikgeUmip`ealmd+The2`?Z#+r#{ zH!5|OUg(Srs%e&9^vp17YnEQ+j6F(%LeG9?52a6`=Yv<nD^6c6!j5qLqYt6hZ=re3 zxun}i(Zb0T*Xz)9<H&lhbeY>sO?SW3nb91%`?hjrj|>Tf1OJM}4i<tux-YQ({h|<L zs5h=Wor-V?zMbtv3Wu}W;)ji1?)P9v(mxi%TJ&svUArUmGjm~;yHPTF*(PgtygGoG zRWBJVJfoU+;>HhN5ruB%85pOUZeX`7HWnHIDCML;hZo{pmr>$ERbKa1HrnC3`}D&N zq{=Xae4@`-VvmWpi|}avoue#{Z`t#h7Z*M!tlQY>>|gP=Y!O{#X^ot-1xON?hJ|Sg zhNmU{z(?9fEc_21L|$V3HiL)=E5+&a@Vw-K^RP2ko$4NjsFxH6wme$9yUQMwZclF0 z0YWWi^MXsf)4oyci#nK6FGY@K`(tandEf3g=-1<LxWJiCrTvP$4xvH<g}__iuE%-% zj`4|$RAj;2K!AbgOm`RP4u^kkovpP~UQL%{3P(x~s&jE5DMKcik>$>UTa4xcI>QFO z4U%l8dce>X18?=_Ry>EqJJXxiOJk}CWtb#fw@I+`KtM;l?dw>5o*7i?X94mC<EOB& zX+a(HmA#QBgA(9k`-QJxC(v2i*c60@snIQrU+ET4HAm;5j8&DFw~}Iu<EG98rRtLr z#=rTZ1;nAlE(fpT=;KmYqLo9LjevKz!1I$EX{Y7G3#U&jqHsKo2|ME1h6BP(vF|~5 zNw)xr3i5i1708A4@g>(<8PWU#ft;>PH&rKpH)*+R9KMYLCH=J##3hW6SP@yGkDuD* zh>6%X69AT(%HSw*0dYLg>cqOpsP^jaYvKj_OoQy*UsCfL$5+6<hz#Kfcve12YN*&X zF)GxFFQR?$IS|uIGodZeRLiMUW#09h`nWnZEy7okV$IuWtjQBF3cA1QgVDJgm0Z1v z!9+$z;#$PBUCw3b5?jhl$;J!aKRqkJn~L$%Y?xED%SFP7ajm#ljKLsPzK8qJ_6r$f z6!&&ymT8{Z1JgYA1gbjxicnR5bbw=@RlB5~Lq!c53lrD$a^N4DIw`Pin{T?kF)rsh z!crahdaBUb1tOf3qL&aQV}0I0DIkrU>@_a|9>jwwR^sBq1tzxVapMC4#*2xoS_NJB zJW1?y>CX`-0+jXi-&cQ|N~$k0B~cZ2WU;t>unaCl5!m1Ndof&F;kPD~xYS3I_b3IV zmCsmMtho`abD$tvJ5il@RFLm429xuwEiyI(8o6&3CYjIJL--6`qrD}gX*)<aS_;ye zYF|lBUGntZ2@1f-l2_8jd=0INbD&bQddULBC{c%<HnC85Dds)M{G^*_UOfmSl4x{e z7o1|~ke^G2CzOHk=uvVW#?A^$)mC7d@#2sw<M6evs09}I%~1-igVCk^iWz!ua?A&O zE$$v~V$;xXy#yO=W~i&Tf9*TU$zC14Xfe`9=m{(U2fi<V$K$|K;&Mt}@sjECf0GjV za^eP+a4h~M!F~F8MOa2Ep|tDV@SM)Jn^NwfN^+{S=?$D97b3@q&>z};4{taQN!QM+ zS|xo1i<zeArrdvCLF^2>qd|6cuJ_A4ufjFEx~l#T#YUwUJGXbEMFyour3vKgZ+zyy zswfo(Q?b={sv=E@5286lhj+}_CBdO?0p`uwC4A19d9N*6EhMJc=KVIzKN5z9?3L!B zn=3P|K8Dh)z$hJpsQKK5{CMk}2i4ChyzI=W3Ep?@nkqL(2fxz^mb2C#eMyKU&ADaU z0fGUy2~(Ndg5wtxHkih`V&Q&FG>s>u+Roy&ozH_;7e5~EdEpznT52;foNbj1GJ8^8 zbuCOLdTl+!4fyYL0}^RclTMr-Zz5LjAj1#G7%a9tw^X(`Jd+wwqdiJn^}Z1SQo}7n zGo@?W^>_RYt30QP_dHKjU<2RjI__(Fdv#z4TQIN9t-vFn-R)6yA3U@7K%!ww=E!47 zsA4bCQr*nOVkRc3zP%N;mgq5{3RbM*ad9GAt4RTm1+&V7ZCos6QB#>-BI3Ren>ZcQ ziwVES6FG)WOd1znK)uIT{j;+LZNda+r1U+8YKA+Wtf*3!Tvqt2d|~z}FXjz2MdEG+ zB*``(C<F=0*hl4nG*-N=Lp9yy^4`7tWr}Cz10fRt9ls}EKpLjTG{FjE4TrJm4<y=r zg2iJJCo~FC$*p_5iR@|Z2pcC6K&#`h<zmY5HcEDxwrTe+m}fQxPADu^dM~V~9h)4Y zj>FbY@GXzs_cNJ}O~2Efc839`oSd-!SKBro5zMY4m*ZUyl_JZ~q_LV$ck%Ix3QDSu zTRf%gvxfEyv(%sn`N!Jp!D;sertB_7tkSC7Rk4|waOzm)cRKGA|6}oWee9_~-?EG% zNlbY9Da}gTsUkz@QJb!XPQkk&N24-(tuzXA-*IQKk>#gj*VwS@<xqBnW<RO8F_0{Y z-6I>gPh~MG=hV)>rFD(>;;knrCXQ3E8xyB_O3(BCX2#roRh?kHmd9Hkmz7>=w5U0C zu38;dqh=)XhdpXi{lECtOkbRv8DmJ*g+NYP;d@eLTZRG8ktz)?tNolY60H-V%S%lb zOT)D4;@Np1^t}Oa*aqoq$3Nn(DYt0%869XcOI8+3f29?^4lS?6RRuP9OP|I~OD+-i zmNbKHmVkg>MKdc9e|i*8wh@<-+!oKal^=Qx$-5!`8T45n5~Uxk@44g?-(zM|T=@v~ zb%mb&VYemu_Z?I8=mD*S!e6VwO(xw*a+Ld=TZoD!xiIoO-J>oamt{f4Kv>|8X-*k- z6UDYlZ*eJ=(o?AvTn0l0uF|Y^sL`=$R0DEqT1_R=o<R4z<S#A*|2(x~_jeTin^P;T z538#Et1=@o2I5tO6<%9Fe&KOPWwiwQTT2^9?~v7eHs&SAYnO^P(?s3VAbH@+p0F{M zcON{7*Ci483EPzOLY9w6v(3Rb=4=8s4#uR?Tj|f>m5-5e5nQN4=-Ho8Uxm-J{n&)* z|8-CH$Jc8pcGt5SNbW}CpRSw!rG;(yv&r$F6_#a@4Uuf9FzNF`$o9w^KR$56or{;g z(=|)vH0=BD7;}*9f+iS(ybI-Cya^POCa;nR;~gL45_P`;%UyE3xgHW(3>?uA<4xun zqb(;k<$d0&Nkg9UsSa$-V8k?z2-Zt<>Pyja@+FKkd~CCSA$D*7UAf1GV}*^oq90+k zg9uzSZ?`aSdVx<HvT@sfXxn2f^R?j<;%L1}r<%Ns6IKLa`1wcRzZYn3`hGc9cj7pa z>?P$r$m5$XfA?-cXZa*-;TP(VnCJL1>CXFXAN`U%+Vi0o1swZ=R<w&o-q~RIqJnz9 zN9#>a22Upv1;IIA%)Hb3eS%hO4i#aEs)2p5`xJ+YRH$fPuj+jKH`R-TmUoCx*iqO$ zG+vWdkQ)|w@v~P(`g*QcYfCkNH#DaG0VJlXu|)j^LhN9!=FYxE!>c=&t7P@k{A*>- z&)rXF5?OqKf!?h)&G{CiVe4YwSvy@*XW<23Eq3E{iQpSq3hnjL>adwicYpib5R5O+ z#mIG_?j)6FAog>b-Rrx|_qJeSN_K_C_yH<Mbj4<Ub`%ot(j;Q<rqBz-;28t`xftFV zUh#_!>u9nm=)>QMBJP%@LzGXKq}_dPVzR5_BxNOVbEcKTg~f}}*}~a}e<a=dKV;ph zNA@2{Z`J)ySK!ZXfH1gjT?I(({7@b?Z0%8Aqvtx=)mxY9DaZ(JG46LA({&fpkuOFR z`~v{St!31`9eTDi(Wv!3+DZEFI8WViBn~hwQvKmynU+(eWqKv454JjN&9v&!CtW+A zvS2&ed_r>vn*3V;<D2qxpuxkHnX-yICv$`0FQu%fp7dvwx-AMP13IF*B{D(v$=YAf zMC^x8^K^Y~O6d)#L+$L+GXtJwW<ayxZ#ed*Jd%oigG6*=5?$WMuQW_LIoBIjWrn-X z!1KY133&;*X{H|Qhix>MW1lAnyMGH~Gpc8s{xP6K{m(Z(ZSi-^`#-yJpJ+|g^L=`5 z)2siclFPhVSw-QZX<-5i_MMJi>2HZYf9*frZkmQCBdUL!UdCC$0wyrN@`K*<H`Byn z?zfTx^<EKku&--Idgks}1|uRd>6|f+4qe_>O(vSA)tGe2_cr-V_yft^dMKH*#Y0Zc zdWl`}BF|^{Dl?f<1@vf3HKT3Evfcao{*tY(3h{NjSI3e}9;lyoXgWAw7i<7KP^g(* z?syw8#?LmjYcLW2e*PHeUj1=e_58p~YakW}uCehJR`3jHmZDbni=f+JgpuU|s-<Rr zB)g*UeG4N4qpOT@pPASZP<GM+LWB0Vu3q-y6_0%Qe>Zs`z}>DTa)GQO^Qq28e^AO^ zP|&w%XawZ5D*VFQ#(M%z?~r|<IQ{BgIpQ?gnQEM59PeSx#cd&2zRQs!HUGEtL$af+ z_vjyk9LoNT`u}IT@-lk<b6NtvJ`E@iJ-y~W5!wj;%Z%6E8=UT`c5{ek_o83nE2KyJ z#T89W?ZgMEJ+Jvmk?^-fZ8}DM-KLj<8)a0a>i)ofxC7#2RGk#&+#Nm>3wA@@47Af- zjJyN`8*YGD9*`0}B*AH$)i!96pg0iIg)P=y#n9_ol8F}+@5CN)xCu?30N-GGvoK9s zzR&XG10W7XdIX?#s!iWdjA>q<>3*kcJ$HuY6C8ey_Th&wyX2Wa?AlRL(&zd9xvKnY zc@d)dHgE}@zpb^(aG5yq(E{F1TaI+oDmmIdzZ1{9^KPzSPx7Sl*!idj^^xog@=L{H zFb>I<#$Pn!sLO$>{X(p%@7DIYR!)t3zDSmqpyqx#`#WmoJG~J%z3E@P17zQmGyhzy zjgmN`cRO<f_2Gz~U3RPfnUDB@^}B-_?k<(f%!(e!4lPZk+DHJUKo<2Hb{Eic7eKiG zen27|0R_L;%J%0Ge9akzGCZZp>Q_kSBh4KOP15*#F20)?-B)|4cxOrM^YIw18PrjJ zCQ`PEiM?5Z5eEfcA{oxoc$;uei!m1S(adxj9~>}LZP!HbDfZJ7U6s`gr~sIi@taBD zO_u-B^f_f1+j%4Ou;U}Gxl#R~V1(^P8Xv8QT&ZR|r^65?es1`hCra`={jF#3jOXpk z#%Ju!QZ-qEcxauMAj7tQH1{#DFamXoBuT=CY1NbK$3}0q=(ApxEvt2XJ%#M?#>nTm z0wjv;7}*E#4-p!x8b;?7o`g3_?3iAmj+&N)ni`sKz)t3SQ0pi3Y+Q<ej?LBnN18NE zR+su>MHMQD2$c8<J<D?+x4(<U=)>86WGY?3`_t6^o+`tiW1EPPpGK5wG?zweK*6L& z8Cq$H^37v5#<zCVA{KHL&h|=CTIuwNq8V!ah@O?Jp!?w0uMHM{+;a<X=_7mfxAuR| z3rhSuJq!FlB<1j&NgwnY{tB`{?4`0A*ZikHUoGyxL7V%n#&gy~?0H`|5wn(bWRkL_ zlkVT9{9xh-2gLtDf{eG}K2w58k?65e;~$BtV{(@GZwBDh`=4~uG!XdM@f0numvOz| zf1#7te@mzEXkKp6vtnEmcpqd>iM>s0`~F)9(*K11!jpG~w*s6o>0^O^@VvBwbtZ=C z{s^-^l=X`i9;eR>P>N&vbOV9PI-+NLv`zQ#l*wksdQ*&@$L>7>ayr~dDDOAXsO#-u zxuzd<XCOw4!hD!R7i|(^0y4$x_Q>#!?AFCyix5SAm~cf|pPsvj{CMFu>Kh|H1IiI` z_J;G1*N_LpQHCSF26&j9V=L8PrA%1j7@M$44Pi_YgB6Yg5{X|-viQR?77NJIAX-CM z&NL*1+o5;e$iUIQ%R#+&ZG~^nD*M^dGoDl>r4ZG(Fx<1OOWQin#g6U?Dbzz$TEnz- zo+REqC;Hc+ulyJ~{8v8ppMFODe1`TWGvmy;%cvU{&)h_@e?P~7T#q+~uh&l1G9PhQ z%5i?JUQA6~?XmOmw$7$mjpSz_tULJCdA+iq#8Ha915y`w$1=6AuEr3D$*=}6*401d zKK8zt^rkTg*g5WKa1CKfj4@(1#bg(tf0p)+?EcBm8}0{%$#C^UM;s2*Q*n9LfOE!P zAY(`W?UaktPf*8>+2Zim7+<N-end!8s((=sL!}vLFlAuNVy>^>f@=sOS}MWP-vtB> z$mV*MX_`u<AGpYXBMS@GC!(&utMTY>V9$gKD>;0~E!@JY=xwE7U9mle$NLz;50uq; z#Ns4eyAE+IciiQi+H#9M=}poUMqxz}Ug2)O(yO+xC3w2)c!d`@bkQ)D?lk^4<kRb{ zNRHngpjPS6-+;5HU;^c>0U#+J1aZ}C>WY(me^kQwtyNt^qpO863RIy*I}40cjDTQJ z#;nFEvSnQEOnY%y*KMX+LY(4B^QmoJ0DH}F&0>&Cd$Ir=x7w{L4*29U3U_cEQ<6Uz zdbu6jgWZ1+WI!nn1((eg9hi+AN58QscJ;yFLN`g(aJ9LSw?|8{3#yu0(<I@Nlo7iZ zFCWC3!5=@{nDrP8_AVN2c<geuQ7B|hd_~TrLQgluG-Pf4)51iFd(6IQzEczXcRHNW zmqFCFO>u#3jM`p?55jCa)!bcd4lczH8Aw+iKH$P|Hdf<b#cJzsf2X^cj6F?M#R*J} z<eKQV!-YP8He7Og)xI`-eR)<2_p%>c3E!wjy^V*vN$n;H<_wXJJA;x@&f=!n6v1RO zO<vS)^5=S*Yg{w4rYS}N8){gHo(wrMEU&dp<Sm4!H)i7FLimjAYEMrMJM=aYTG*!~ z$mVyt6s3|#YW?+;)bz6etAWj)(o?lv$tOMHiO!_kBmHXC4wOVDv51qqnTaZeA;4Z- zapAZz2sfK|XN>(y!n~Fh(%k-Mo:Z#*k}!`H36Uy;4<XaHUN>5*<{j;UtHtpBPx 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ɚ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 delta 286745 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⩔+Z6`#+xei-r9N+Z!Z!ASgUc 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�{`M@Oo~OH| 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¬J3p+vui*#-uzp@v*KO@x{cRVFH7ev+>?(+d`5m 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 X<iQ4k1Hb0=f8p0f2>(j!{W1Jc5N%n( From bd0ce85f97f91a7aecf2c03214f089e438c113b5 Mon Sep 17 00:00:00 2001 From: buyuxiang <347586493@qq.com> Date: Thu, 12 May 2022 21:37:30 +0800 Subject: [PATCH 037/109] feat: postman2case --- README.en.md | 2 +- README.md | 2 +- docs/CHANGELOG.md | 1 + .../data/postman2case/postman_collection.json | 346 +++++++++++++++++ hrp/cmd/postman2case.go | 65 ++++ hrp/internal/postman2case/collection.go | 74 ++++ hrp/internal/postman2case/core.go | 364 ++++++++++++++++++ hrp/internal/postman2case/core_test.go | 39 ++ 8 files changed, 891 insertions(+), 2 deletions(-) create mode 100644 examples/data/postman2case/postman_collection.json create mode 100644 hrp/cmd/postman2case.go create mode 100644 hrp/internal/postman2case/collection.go create mode 100644 hrp/internal/postman2case/core.go create mode 100644 hrp/internal/postman2case/core_test.go diff --git a/README.en.md b/README.en.md index d1a564e0..555de35a 100644 --- a/README.en.md +++ b/README.en.md @@ -108,7 +108,7 @@ Use "hrp [command] --help" for more information about a command. 关注 HttpRunner 的微信公众号,第一时间获得最新资讯。 -<img src="docs/assets/qrcode.jpg" alt="HttpRunner" width="200"> +<img src="https://httprunner.com/image/qrcode.png" alt="HttpRunner" width="400"> 如果你期望加入 HttpRunner 核心用户群,请填写[用户调研问卷][survey]并留下你的联系方式,作者将拉你进群。 diff --git a/README.md b/README.md index 0581a634..f8949730 100644 --- a/README.md +++ b/README.md @@ -117,7 +117,7 @@ HttpRunner is in Sentry Sponsored plan. 关注 HttpRunner 的微信公众号,第一时间获得最新资讯。 -<img src="https://httprunner.com/image/qrcode.jpg" alt="HttpRunner" width="200"> +<img src="https://httprunner.com/image/qrcode.png" alt="HttpRunner" width="400"> 如果你期望加入 HttpRunner 核心用户群,请填写[用户调研问卷][survey]并留下你的联系方式,作者将拉你进群。 diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index b3dc23dd..118ede44 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -8,6 +8,7 @@ - fix: step request elapsed timing should contain ContentTransfer part - fix #1288: unable to go get httprunner v4 +- feat: support converting Postman collection to HttpRunner testcase **python version** diff --git a/examples/data/postman2case/postman_collection.json b/examples/data/postman2case/postman_collection.json new file mode 100644 index 00000000..5cedbcf8 --- /dev/null +++ b/examples/data/postman2case/postman_collection.json @@ -0,0 +1,346 @@ +{ + "info": { + "_postman_id": "0417a445-b206-4ea2-b1d2-5441afd6c6b9", + "name": "postman collection demo", + "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" + }, + "item": [ + { + "name": "folder1", + "item": [ + { + "name": "folder2", + "item": [ + { + "name": "Get with params", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "https://postman-echo.com/:path?k1=v1&k2=v2", + "protocol": "https", + "host": [ + "postman-echo", + "com" + ], + "path": [ + ":path" + ], + "query": [ + { + "key": "k1", + "value": "v1" + }, + { + "key": "k2", + "value": "v2" + }, + { + "key": "k3", + "value": "v3", + "disabled": true + } + ], + "variable": [ + { + "key": "path", + "value": "get" + } + ] + } + }, + "response": [ + { + "name": "Get with params", + "originalRequest": { + "method": "GET", + "header": [], + "url": { + "raw": "https://postman-echo.com/:path?k1=v1&k2=v2", + "protocol": "https", + "host": [ + "postman-echo", + "com" + ], + "path": [ + ":path" + ], + "query": [ + { + "key": "k1", + "value": "v1" + }, + { + "key": "k2", + "value": "v2" + }, + { + "key": "k3", + "value": "v3", + "disabled": true + } + ], + "variable": [ + { + "key": "path", + "value": "get" + } + ] + } + }, + "_postman_previewlanguage": "json", + "header": null, + "cookie": [], + "body": "{\n \"args\": {\n \"k1\": \"v1\",\n \"k2\": \"v2\"\n },\n \"headers\": {\n \"x-forwarded-proto\": \"https\",\n \"x-forwarded-port\": \"443\",\n \"host\": \"postman-echo.com\",\n \"user-agent\": \"PostmanRuntime/7.29.0\",\n \"accept\": \"*/*\",\n \"accept-encoding\": \"gzip, deflate, br\"\n },\n \"url\": \"https://postman-echo.com/get?k1=v1&k2=v2\"\n}" + } + ] + } + ] + } + ] + }, + { + "name": "folder3", + "item": [ + { + "name": "Post form-data", + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "formdata", + "formdata": [ + { + "key": "k1", + "value": "v1", + "type": "text" + }, + { + "key": "k2", + "value": "v2", + "type": "text" + }, + { + "key": "k3", + "value": "v3", + "type": "text", + "disabled": true + } + ] + }, + "url": { + "raw": "https://postman-echo.com/:path", + "protocol": "https", + "host": [ + "postman-echo", + "com" + ], + "path": [ + ":path" + ], + "variable": [ + { + "key": "path", + "value": "post" + } + ] + } + }, + "response": [] + }, + { + "name": "Post x-www-form-urlencoded", + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "urlencoded", + "urlencoded": [ + { + "key": "k1", + "value": "v1", + "type": "text" + }, + { + "key": "k2", + "value": "v2", + "type": "text" + }, + { + "key": "k3", + "value": "v3", + "type": "text", + "disabled": true + } + ] + }, + "url": { + "raw": "https://postman-echo.com/:path", + "protocol": "https", + "host": [ + "postman-echo", + "com" + ], + "path": [ + ":path" + ], + "variable": [ + { + "key": "path", + "value": "post" + } + ] + } + }, + "response": [] + }, + { + "name": "Post raw json", + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"k1\": \"v1\",\n \"k2\": \"v2\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "https://postman-echo.com/:path", + "protocol": "https", + "host": [ + "postman-echo", + "com" + ], + "path": [ + ":path" + ], + "variable": [ + { + "key": "path", + "value": "post" + } + ] + } + }, + "response": [] + }, + { + "name": "Post raw text", + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "have a nice day", + "options": { + "raw": { + "language": "text" + } + } + }, + "url": { + "raw": "https://postman-echo.com/:path", + "protocol": "https", + "host": [ + "postman-echo", + "com" + ], + "path": [ + ":path" + ], + "variable": [ + { + "key": "path", + "value": "post" + } + ] + } + }, + "response": [] + } + ] + }, + { + "name": "Get request headers", + "request": { + "method": "GET", + "header": [ + { + "key": "User-Agent", + "value": "HttpRunner", + "type": "text" + }, + { + "key": "User-Name", + "value": "bbx", + "type": "text", + "disabled": true + } + ], + "url": { + "raw": "https://postman-echo.com/:path", + "protocol": "https", + "host": [ + "postman-echo", + "com" + ], + "path": [ + ":path" + ], + "variable": [ + { + "key": "path", + "value": "headers" + } + ] + } + }, + "response": [ + { + "name": "Get request headers", + "originalRequest": { + "method": "GET", + "header": [ + { + "key": "User-Agent", + "value": "HttpRunner", + "type": "text" + }, + { + "key": "User-Name", + "value": "bbx", + "type": "text", + "disabled": true + } + ], + "url": { + "raw": "https://postman-echo.com/:path", + "protocol": "https", + "host": [ + "postman-echo", + "com" + ], + "path": [ + ":path" + ], + "variable": [ + { + "key": "path", + "value": "headers" + } + ] + } + }, + "_postman_previewlanguage": "json", + "header": null, + "cookie": [], + "body": "{\n \"headers\": {\n \"x-forwarded-proto\": \"https\",\n \"x-forwarded-port\": \"443\",\n \"host\": \"postman-echo.com\",\n \"user-agent\": \"HttpRunner\",\n \"accept\": \"*/*\",\n \"accept-encoding\": \"gzip, deflate, br\"\n }\n}" + } + ] + } + ] +} \ No newline at end of file diff --git a/hrp/cmd/postman2case.go b/hrp/cmd/postman2case.go new file mode 100644 index 00000000..5ccedacb --- /dev/null +++ b/hrp/cmd/postman2case.go @@ -0,0 +1,65 @@ +package cmd + +import ( + "errors" + + "github.com/rs/zerolog/log" + "github.com/spf13/cobra" + + "github.com/httprunner/httprunner/v4/hrp/internal/postman2case" +) + +// postman2caseCmd represents the postman2case command +var postman2caseCmd = &cobra.Command{ + Use: "postman2case $postman_path...", + Short: "convert postman collection to json/yaml testcase files", + Long: `convert postman collection to json/yaml testcase files`, + Args: cobra.MinimumNArgs(1), + PreRun: func(cmd *cobra.Command, args []string) { + setLogLevel(logLevel) + }, + RunE: func(cmd *cobra.Command, args []string) error { + var outputFiles []string + for _, arg := range args { + // must choose one + if !postman2JSONFlag && !postman2YAMLFlag { + return errors.New("please select convert format type") + } + var outputPath string + var err error + + postman := postman2case.NewCollection(arg) + + // specify output dir + if postman2Dir != "" { + postman.SetOutputDir(postman2Dir) + } + + // generate json/yaml files + if genYAMLFlag { + outputPath, err = postman.GenYAML() + } else { + outputPath, err = postman.GenJSON() // default + } + if err != nil { + return err + } + outputFiles = append(outputFiles, outputPath) + } + log.Info().Strs("output", outputFiles).Msg("convert testcase success") + return nil + }, +} + +var ( + postman2JSONFlag bool + postman2YAMLFlag bool + postman2Dir string +) + +func init() { + rootCmd.AddCommand(postman2caseCmd) + postman2caseCmd.Flags().BoolVarP(&postman2JSONFlag, "to-json", "j", true, "convert to JSON format") + postman2caseCmd.Flags().BoolVarP(&postman2YAMLFlag, "to-yaml", "y", false, "convert to YAML format") + postman2caseCmd.Flags().StringVarP(&postman2Dir, "output-dir", "d", "", "specify output directory, default to the same dir with postman collection file") +} diff --git a/hrp/internal/postman2case/collection.go b/hrp/internal/postman2case/collection.go new file mode 100644 index 00000000..ddabee21 --- /dev/null +++ b/hrp/internal/postman2case/collection.go @@ -0,0 +1,74 @@ +package postman2case + +/* +Postman Collection format reference: +https://schema.postman.com/json/collection/v2.0.0/collection.json +https://schema.postman.com/json/collection/v2.1.0/collection.json +*/ + +// TCollection represents the postman exported file +type TCollection struct { + Info TInfo `json:"info"` + Items []TItem `json:"item"` +} + +// TInfo gives information about the collection +type TInfo struct { + Name string `json:"name"` + Description string `json:"description"` + Schema string `json:"schema"` +} + +// TItem contains the detail information of request and expected responses +// item could be defined recursively +type TItem struct { + Items []TItem `json:"item"` + Name string `json:"name"` + Request TRequest `json:"request"` + Responses []TResponse `json:"response"` +} + +type TRequest struct { + Method string `json:"method"` + Headers []TField `json:"header"` + Body TBody `json:"body"` + URL TUrl `json:"url"` + Description string `json:"description"` +} + +type TResponse struct { + Name string `json:"name"` + OriginalRequest TRequest `json:"originalRequest"` + Status string `json:"status"` + Code int `json:"code"` + Headers []TField `json:"headers"` + Body string `json:"body"` +} + +type TUrl struct { + Raw string `json:"raw"` + Protocol string `json:"protocol"` + Path []string `json:"path"` + Description string `json:"description"` + Query []TField `json:"query"` + Variable []TField `json:"variable"` +} + +type TField struct { + Key string `json:"key"` + Value string `json:"value"` + Src string `json:"src"` + Description string `json:"description"` + Type string `json:"type"` + Disabled bool `json:"disabled"` + Enable bool `json:"enable"` +} + +type TBody struct { + Mode string `json:"mode"` + FormData []TField `json:"formdata"` + URLEncoded []TField `json:"urlencoded"` + Raw string `json:"raw"` + Disabled bool `json:"disabled"` + Options interface{} `json:"options"` +} diff --git a/hrp/internal/postman2case/core.go b/hrp/internal/postman2case/core.go new file mode 100644 index 00000000..f4b8b4e3 --- /dev/null +++ b/hrp/internal/postman2case/core.go @@ -0,0 +1,364 @@ +package postman2case + +import ( + "bytes" + "fmt" + "github.com/httprunner/httprunner/v4/hrp/internal/json" + "io" + "mime/multipart" + "net/url" + "os" + "path/filepath" + "reflect" + "strings" + + "github.com/pkg/errors" + "github.com/rs/zerolog/log" + + "github.com/httprunner/httprunner/v4/hrp" + "github.com/httprunner/httprunner/v4/hrp/internal/builtin" +) + +const ( + enumBodyRaw = "raw" + enumBodyUrlEncoded = "urlencoded" + enumBodyFormData = "formdata" + enumBodyFile = "file" + enumBodyGraphQL = "graphql" +) + +const ( + enumFieldTypeText = "text" + enumFieldTypeFile = "file" +) + +const ( + suffixName = ".converted" + extensionJSON = ".json" + extensionYAML = ".yaml" +) + +var contentTypeMap = map[string]string{ + "text": "text/plain", + "javascript": "application/javascript", + "json": "application/json", + "html": "text/html", + "xml": "application/xml", +} + +func NewCollection(path string) *collection { + return &collection{ + path: path, + } +} + +type collection struct { + path string + outputDir string +} + +func (c *collection) SetOutputDir(dir string) { + log.Info().Str("dir", dir).Msg("set output directory") + c.outputDir = dir +} + +func (c *collection) GenJSON() (jsonPath string, err error) { + testCase, err := c.makeTestCase() + if err != nil { + return "", err + } + jsonPath = c.genOutputPath(extensionJSON) + err = builtin.Dump2JSON(testCase, jsonPath) + return +} + +func (c *collection) GenYAML() (yamlPath string, err error) { + testCase, err := c.makeTestCase() + if err != nil { + return "", err + } + yamlPath = c.genOutputPath(extensionYAML) + err = builtin.Dump2YAML(testCase, yamlPath) + return +} + +func (c *collection) genOutputPath(suffix string) string { + file := getFilenameWithoutExtension(c.path) + suffix + if c.outputDir != "" { + return filepath.Join(c.outputDir, file) + } else { + return filepath.Join(filepath.Dir(c.path), file) + } +} + +func getFilenameWithoutExtension(path string) string { + base := filepath.Base(path) + ext := filepath.Ext(base) + return base[0:len(base)-len(ext)] + suffixName +} + +func (c *collection) makeTestCase() (*hrp.TCase, error) { + tCollection, err := c.load() + if err != nil { + return nil, err + } + teststeps, err := c.prepareTestSteps(tCollection) + if err != nil { + return nil, err + } + tCase := &hrp.TCase{ + Config: c.prepareConfig(tCollection), + TestSteps: teststeps, + } + return tCase, nil +} + +func (c *collection) load() (*TCollection, error) { + collection := &TCollection{} + err := builtin.LoadFile(c.path, collection) + if err != nil { + return nil, errors.Wrap(err, "load postman collection failed") + } + return collection, nil +} + +func (c *collection) prepareConfig(tCollection *TCollection) *hrp.TConfig { + return hrp.NewConfig(tCollection.Info.Name). + SetVerifySSL(false) +} + +func (c *collection) prepareTestSteps(tCollection *TCollection) ([]*hrp.TStep, error) { + // recursively convert collection items into a list + var itemList []TItem + for _, item := range tCollection.Items { + extractItemList(item, &itemList) + } + + var steps []*hrp.TStep + for _, item := range itemList { + step, err := c.prepareTestStep(&item) + if err != nil { + return nil, err + } + steps = append(steps, step) + } + return steps, nil +} + +func extractItemList(item TItem, itemList *[]TItem) { + // current item contains no other items and request is not empty + if len(item.Items) == 0 { + if !reflect.DeepEqual(item.Request, TRequest{}) { + *itemList = append(*itemList, item) + } + return + } + + // look up all items inside + for _, i := range item.Items { + // append item name + i.Name = fmt.Sprintf("%s - %s", item.Name, i.Name) + extractItemList(i, itemList) + } +} + +func (c *collection) prepareTestStep(item *TItem) (*hrp.TStep, error) { + log.Info(). + Str("method", item.Request.Method). + Str("url", item.Request.URL.Raw). + Msg("convert teststep") + + step := &tStep{ + hrp.TStep{ + Request: &hrp.Request{}, + Validators: make([]interface{}, 0), + }, + } + if err := step.makeRequestName(item); err != nil { + return nil, err + } + if err := step.makeRequestMethod(item); err != nil { + return nil, err + } + if err := step.makeRequestURL(item); err != nil { + return nil, err + } + if err := step.makeRequestParams(item); err != nil { + return nil, err + } + if err := step.makeRequestHeadersAndCookies(item); err != nil { + return nil, err + } + if err := step.makeRequestBody(item); err != nil { + return nil, err + } + if err := step.makeValidate(item); err != nil { + return nil, err + } + return &step.TStep, nil +} + +type tStep struct { + hrp.TStep +} + +// makeRequestName indicates the step name the same as item name +func (s *tStep) makeRequestName(item *TItem) error { + s.Name = item.Name + return nil +} + +func (s *tStep) makeRequestMethod(item *TItem) error { + s.Request.Method = hrp.HTTPMethod(item.Request.Method) + return nil +} + +func (s *tStep) makeRequestURL(item *TItem) error { + rawUrl := item.Request.URL.Raw + // parse path variables like ":path" in https://postman-echo.com/:path?k1=v1&k2=v2 + for _, field := range item.Request.URL.Variable { + pathVar := ":" + field.Key + rawUrl = strings.Replace(rawUrl, pathVar, field.Value, -1) + } + u, err := url.Parse(rawUrl) + if err != nil { + return errors.Wrap(err, "parse URL error") + } + s.Request.URL = fmt.Sprintf("%s://%s", u.Scheme, u.Host+u.Path) + return nil +} + +func (s *tStep) makeRequestParams(item *TItem) error { + s.Request.Params = make(map[string]interface{}) + for _, field := range item.Request.URL.Query { + if field.Disabled { + continue + } + s.Request.Params[field.Key] = field.Value + } + return nil +} + +func (s *tStep) makeRequestHeadersAndCookies(item *TItem) error { + s.Request.Headers = make(map[string]string) + for _, field := range item.Request.Headers { + if field.Disabled { + continue + } + if strings.EqualFold(field.Key, "cookie") { + s.Request.Cookies[field.Key] = field.Value + continue + } + s.Request.Headers[field.Key] = field.Value + } + return nil +} + +func (s *tStep) makeRequestBody(item *TItem) error { + mode := item.Request.Body.Mode + if mode == "" { + return nil + } + switch mode { + case enumBodyRaw: + return s.makeRequestBodyRaw(item) + case enumBodyFormData: + return s.makeRequestBodyFormData(item) + case enumBodyUrlEncoded: + return s.makeRequestBodyUrlEncoded(item) + case enumBodyFile, enumBodyGraphQL: + return errors.New("not supported body type") + } + return nil +} + +func (s *tStep) makeRequestBodyRaw(item *TItem) (err error) { + defer func() { + if p := recover(); p != nil { + err = fmt.Errorf("make request body raw failed: %v", p) + } + }() + + // extract language type + iOptions := item.Request.Body.Options + iLanguage := iOptions.(map[string]interface{})["raw"] + languageType := iLanguage.(map[string]interface{})["language"].(string) + + // make request body and indicate Content-Type + rawBody := item.Request.Body.Raw + if languageType == "json" { + var iBody interface{} + err = json.Unmarshal([]byte(rawBody), &iBody) + if err != nil { + return errors.Wrap(err, "make request body raw failed") + } + s.Request.Body = iBody + } else { + s.Request.Body = rawBody + } + s.Request.Headers["Content-Type"] = contentTypeMap[languageType] + return +} + +func (s *tStep) makeRequestBodyFormData(item *TItem) (err error) { + defer func() { + if err != nil { + err = errors.Wrap(err, "make request body form-data failed") + } + }() + payload := &bytes.Buffer{} + writer := multipart.NewWriter(payload) + for _, field := range item.Request.Body.FormData { + if field.Disabled { + continue + } + // form data could be text or file + if field.Type == enumFieldTypeText { + err = writer.WriteField(field.Key, field.Value) + if err != nil { + return + } + } else if field.Type == enumFieldTypeFile { + err = writeFormDataFile(writer, &field) + if err != nil { + return + } + } + } + err = writer.Close() + s.Request.Body = payload.String() + s.Request.Headers["Content-Type"] = writer.FormDataContentType() + return +} + +func writeFormDataFile(writer *multipart.Writer, field *TField) error { + file, err := os.Open(field.Src) + if err != nil { + return err + } + defer file.Close() + formFile, err := writer.CreateFormFile(field.Key, filepath.Base(field.Src)) + if err != nil { + return err + } + _, err = io.Copy(formFile, file) + return err +} + +func (s *tStep) makeRequestBodyUrlEncoded(item *TItem) error { + payloadMap := make(map[string]string) + for _, field := range item.Request.Body.URLEncoded { + if field.Disabled { + continue + } + payloadMap[field.Key] = field.Value + } + s.Request.Body = payloadMap + s.Request.Headers["Content-Type"] = "application/x-www-form-urlencoded" + return nil +} + +// TODO makeValidate from example response +func (s *tStep) makeValidate(item *TItem) error { + return nil +} diff --git a/hrp/internal/postman2case/core_test.go b/hrp/internal/postman2case/core_test.go new file mode 100644 index 00000000..47b9eabc --- /dev/null +++ b/hrp/internal/postman2case/core_test.go @@ -0,0 +1,39 @@ +package postman2case + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +var collectionPath = "../../../examples/data/postman2case/postman_collection.json" + +func TestLoadPostmanCollection(t *testing.T) { + c, err := NewCollection(collectionPath).load() + if !assert.NoError(t, err) { + t.Fatal(err) + } + if !assert.Equal(t, "postman collection demo", c.Info.Name) { + t.Fatal() + } +} + +func TestGenJSON(t *testing.T) { + jsonPath, err := NewCollection(collectionPath).GenJSON() + if !assert.NoError(t, err) { + t.Fatal() + } + if !assert.NotEmpty(t, jsonPath) { + t.Fatal() + } +} + +func TestGenYAML(t *testing.T) { + yamlPath, err := NewCollection(collectionPath).GenYAML() + if !assert.NoError(t, err) { + t.Fatal() + } + if !assert.NotEmpty(t, yamlPath) { + t.Fatal() + } +} From e0430f495ec8c0d3226ec3b685d6674261bc9fde Mon Sep 17 00:00:00 2001 From: xucong053 <xucong053@outlook.com> Date: Fri, 13 May 2022 18:09:47 +0800 Subject: [PATCH 038/109] fix: panic when config didn't exist in testcase file --- hrp/testcase.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/hrp/testcase.go b/hrp/testcase.go index d9a241de..05c2b051 100644 --- a/hrp/testcase.go +++ b/hrp/testcase.go @@ -60,6 +60,9 @@ func (path *TestCasePath) ToTestCase() (*TestCase, error) { if err != nil { return nil, err } + if tc.Config == nil { + return nil, errors.New("incorrect testcase file format, expected config in file") + } err = tc.makeCompat() if err != nil { From 115b5cbc3fc135c4aa91d5f19e9d21ccc6148d87 Mon Sep 17 00:00:00 2001 From: buyuxiang <347586493@qq.com> Date: Tue, 17 May 2022 16:03:45 +0800 Subject: [PATCH 039/109] add unittest; add --patch options --- docs/cmd/hrp_postman2case.md | 26 +++ examples/data/har/demo.json | 128 --------------- .../{postman_collection.json => demo.json} | 154 ++++++++++++++++- examples/data/postman2case/patch.yml | 4 + examples/data/postman2case/profile.yml | 4 + hrp/cmd/convert.go | 7 +- hrp/cmd/har2case.go | 37 +++-- hrp/cmd/postman2case.go | 42 +++-- .../convert/{ => case2script}/main.go | 2 +- .../convert/{ => case2script}/testcase.tmpl | 0 hrp/internal/{ => convert}/har2case/README.md | 0 hrp/internal/{ => convert}/har2case/core.go | 87 +++++++--- .../{ => convert}/har2case/core_test.go | 36 +++- hrp/internal/{ => convert}/har2case/har.go | 0 .../{ => convert}/postman2case/collection.go | 0 .../{ => convert}/postman2case/core.go | 131 +++++++++++++-- .../convert/postman2case/core_test.go | 155 ++++++++++++++++++ hrp/internal/postman2case/core_test.go | 39 ----- 18 files changed, 604 insertions(+), 248 deletions(-) create mode 100644 docs/cmd/hrp_postman2case.md delete mode 100644 examples/data/har/demo.json rename examples/data/postman2case/{postman_collection.json => demo.json} (58%) create mode 100644 examples/data/postman2case/patch.yml create mode 100644 examples/data/postman2case/profile.yml rename hrp/internal/convert/{ => case2script}/main.go (99%) rename hrp/internal/convert/{ => case2script}/testcase.tmpl (100%) rename hrp/internal/{ => convert}/har2case/README.md (100%) rename hrp/internal/{ => convert}/har2case/core.go (84%) rename hrp/internal/{ => convert}/har2case/core_test.go (90%) rename hrp/internal/{ => convert}/har2case/har.go (100%) rename hrp/internal/{ => convert}/postman2case/collection.go (100%) rename hrp/internal/{ => convert}/postman2case/core.go (72%) create mode 100644 hrp/internal/convert/postman2case/core_test.go delete mode 100644 hrp/internal/postman2case/core_test.go diff --git a/docs/cmd/hrp_postman2case.md b/docs/cmd/hrp_postman2case.md new file mode 100644 index 00000000..23c196e7 --- /dev/null +++ b/docs/cmd/hrp_postman2case.md @@ -0,0 +1,26 @@ +## hrp postman2case + +convert postman collection to json/yaml testcase files + +### Synopsis + +convert postman collection to json/yaml testcase files + +``` +hrp postman2case $postman_path... [flags] +``` + +### Options + +``` + -h, --help help for postman2case + -d, --output-dir string specify output directory, default to the same dir with postman collection file + -j, --to-json convert to JSON format (default true) + -y, --to-yaml convert to YAML format +``` + +### SEE ALSO + +* [hrp](hrp.md) - Next-Generation API Testing Solution. + +###### Auto generated by spf13/cobra on 12-May-2022 diff --git a/examples/data/har/demo.json b/examples/data/har/demo.json deleted file mode 100644 index 292ad513..00000000 --- a/examples/data/har/demo.json +++ /dev/null @@ -1,128 +0,0 @@ -{ - "config": { - "name": "testcase description" - }, - "teststeps": [ - { - "name": "", - "request": { - "method": "GET", - "url": "https://postman-echo.com/get", - "params": { - "foo1": "HDnY8", - "foo2": "34.5" - }, - "headers": { - "Accept-Encoding": "gzip", - "Host": "postman-echo.com", - "User-Agent": "HttpRunnerPlus" - } - }, - "validate": [ - { - "check": "status_code", - "assert": "equals", - "expect": 200, - "msg": "assert response status code" - }, - { - "check": "headers.\"Content-Type\"", - "assert": "equals", - "expect": "application/json; charset=utf-8", - "msg": "assert response header Content-Type" - }, - { - "check": "body.url", - "assert": "equals", - "expect": "https://postman-echo.com/get?foo1=HDnY8\u0026foo2=34.5", - "msg": "assert response body url" - } - ] - }, - { - "name": "", - "request": { - "method": "POST", - "url": "https://postman-echo.com/post", - "headers": { - "Accept-Encoding": "gzip", - "Content-Length": "28", - "Content-Type": "application/json; charset=UTF-8", - "Host": "postman-echo.com", - "User-Agent": "Go-http-client/1.1" - }, - "cookies": { - "sails.sid": "s%3Az_LpglkKxTvJ_eHVUH6V67drKp0AGWW-.PidabaXOnatLRP47hVyqqepl6BdrpEQzRlJQXtbIiwk" - }, - "body": { - "foo1": "HDnY8", - "foo2": 12.3 - } - }, - "validate": [ - { - "check": "status_code", - "assert": "equals", - "expect": 200, - "msg": "assert response status code" - }, - { - "check": "headers.\"Content-Type\"", - "assert": "equals", - "expect": "application/json; charset=utf-8", - "msg": "assert response header Content-Type" - }, - { - "check": "body.url", - "assert": "equals", - "expect": "https://postman-echo.com/post", - "msg": "assert response body url" - } - ] - }, - { - "name": "", - "request": { - "method": "POST", - "url": "https://postman-echo.com/post", - "headers": { - "Accept-Encoding": "gzip", - "Content-Length": "20", - "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8", - "Host": "postman-echo.com", - "User-Agent": "Go-http-client/1.1" - }, - "cookies": { - "sails.sid": "s%3AS5e7w0zQ0xAsCwh9L8T6R7QLYCO7_gtD.r8%2B2w9IWqEIfuVkrZjnxzm2xADIk34zKAWXRPapr%2FAw" - }, - "body": "foo1=HDnY8\u0026foo2=12.3" - }, - "validate": [ - { - "check": "status_code", - "assert": "equals", - "expect": 200, - "msg": "assert response status code" - }, - { - "check": "headers.\"Content-Type\"", - "assert": "equals", - "expect": "application/json; charset=utf-8", - "msg": "assert response header Content-Type" - }, - { - "check": "body.data", - "assert": "equals", - "expect": "", - "msg": "assert response body data" - }, - { - "check": "body.url", - "assert": "equals", - "expect": "https://postman-echo.com/post", - "msg": "assert response body url" - } - ] - } - ] -} \ No newline at end of file diff --git a/examples/data/postman2case/postman_collection.json b/examples/data/postman2case/demo.json similarity index 58% rename from examples/data/postman2case/postman_collection.json rename to examples/data/postman2case/demo.json index 5cedbcf8..3b7a9e30 100644 --- a/examples/data/postman2case/postman_collection.json +++ b/examples/data/postman2case/demo.json @@ -51,7 +51,7 @@ }, "response": [ { - "name": "Get with params", + "name": "Get with params case1", "originalRequest": { "method": "GET", "header": [], @@ -88,10 +88,115 @@ ] } }, + "status": "OK", + "code": 200, "_postman_previewlanguage": "json", - "header": null, + "header": [ + { + "key": "Date", + "value": "Mon, 16 May 2022 12:12:28 GMT" + }, + { + "key": "Content-Type", + "value": "application/json; charset=utf-8" + }, + { + "key": "Content-Length", + "value": "508" + }, + { + "key": "Connection", + "value": "keep-alive" + }, + { + "key": "ETag", + "value": "W/\"1fc-x4EIPFQzoLX0HenCFPx6HNfG0lc\"" + }, + { + "key": "Vary", + "value": "Accept-Encoding" + }, + { + "key": "set-cookie", + "value": "sails.sid=s%3AX2aa_Z7gbcUqIWAjlBkytBRmQ4WCvc3D.pX9Qxh8aO9Ict0BL4CrRhdDJmz81UVmwFsV5Nx30Ils; Path=/; HttpOnly" + } + ], "cookie": [], - "body": "{\n \"args\": {\n \"k1\": \"v1\",\n \"k2\": \"v2\"\n },\n \"headers\": {\n \"x-forwarded-proto\": \"https\",\n \"x-forwarded-port\": \"443\",\n \"host\": \"postman-echo.com\",\n \"user-agent\": \"PostmanRuntime/7.29.0\",\n \"accept\": \"*/*\",\n \"accept-encoding\": \"gzip, deflate, br\"\n },\n \"url\": \"https://postman-echo.com/get?k1=v1&k2=v2\"\n}" + "body": "{\n \"args\": {\n \"k1\": \"v1\",\n \"k2\": \"v2\"\n },\n \"headers\": {\n \"x-forwarded-proto\": \"https\",\n \"x-forwarded-port\": \"443\",\n \"host\": \"postman-echo.com\",\n \"user-agent\": \"PostmanRuntime/7.29.0\",\n \"accept\": \"*/*\",\n \"accept-encoding\": \"gzip, deflate, br\",\n \"cookie\": \"Cookie_1=c1; Cookie_2=c2; sails.sid=s%3AGX6aS9b_phvUSUk66w7ZBgWuOPI7IIKT.ayEGTaW4U35eAWyPz%2Fh6Q74DonNcbqw3H5Q5Zv%2BfKMY\"\n },\n \"url\": \"https://postman-echo.com/get?k1=v1&k2=v2\"\n}" + }, + { + "name": "Get with params case2", + "originalRequest": { + "method": "GET", + "header": [], + "url": { + "raw": "https://postman-echo.com/:path?k1=v1&k3=v3", + "protocol": "https", + "host": [ + "postman-echo", + "com" + ], + "path": [ + ":path" + ], + "query": [ + { + "key": "k1", + "value": "v1" + }, + { + "key": "k2", + "value": "v2", + "disabled": true + }, + { + "key": "k3", + "value": "v3" + } + ], + "variable": [ + { + "key": "path", + "value": "get" + } + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Date", + "value": "Mon, 16 May 2022 12:14:04 GMT" + }, + { + "key": "Content-Type", + "value": "application/json; charset=utf-8" + }, + { + "key": "Content-Length", + "value": "504" + }, + { + "key": "Connection", + "value": "keep-alive" + }, + { + "key": "ETag", + "value": "W/\"1f8-tMaKs4xmwr+3su3I8mcgR0p+ucw\"" + }, + { + "key": "Vary", + "value": "Accept-Encoding" + }, + { + "key": "set-cookie", + "value": "sails.sid=s%3AMNuX_i0KgaP_KuuMpYB8RtCNipCGJWVw.4ETfPHxE81Omqb6Yli%2FezUU8CXyYBcN3%2Bxkx5htwh8Y; Path=/; HttpOnly" + } + ], + "cookie": [], + "body": "{\n \"args\": {\n \"k1\": \"v1\",\n \"k3\": \"v3\"\n },\n \"headers\": {\n \"x-forwarded-proto\": \"https\",\n \"x-forwarded-port\": \"443\",\n \"host\": \"postman-echo.com\",\n \"user-agent\": \"PostmanRuntime/7.29.0\",\n \"accept\": \"*/*\",\n \"accept-encoding\": \"gzip, deflate, br\",\n \"cookie\": \"Cookie_1=c1; Cookie_2=c2; sails.sid=s%3AX2aa_Z7gbcUqIWAjlBkytBRmQ4WCvc3D.pX9Qxh8aO9Ict0BL4CrRhdDJmz81UVmwFsV5Nx30Ils\"\n },\n \"url\": \"https://postman-echo.com/get?k1=v1&k3=v3\"\n}" } ] } @@ -279,6 +384,11 @@ "value": "bbx", "type": "text", "disabled": true + }, + { + "key": "Connection", + "value": "close", + "type": "text" } ], "url": { @@ -301,7 +411,7 @@ }, "response": [ { - "name": "Get request headers", + "name": "Get request headers case1", "originalRequest": { "method": "GET", "header": [ @@ -315,6 +425,11 @@ "value": "bbx", "type": "text", "disabled": true + }, + { + "key": "Cookie", + "value": "Cookie_1=c1; Cookie_2=c2; sails.sid=s%3AGX6aS9b_phvUSUk66w7ZBgWuOPI7IIKT.ayEGTaW4U35eAWyPz%2Fh6Q74DonNcbqw3H5Q5Zv%2BfKMY", + "type": "text" } ], "url": { @@ -335,10 +450,37 @@ ] } }, + "status": "OK", + "code": 200, "_postman_previewlanguage": "json", - "header": null, + "header": [ + { + "key": "Date", + "value": "Mon, 16 May 2022 12:14:25 GMT" + }, + { + "key": "Content-Type", + "value": "application/json; charset=utf-8" + }, + { + "key": "Content-Length", + "value": "541" + }, + { + "key": "Connection", + "value": "keep-alive" + }, + { + "key": "ETag", + "value": "W/\"21d-ld5UvFTaRM6lihVnvCj6mZm5Of0\"" + }, + { + "key": "Vary", + "value": "Accept-Encoding" + } + ], "cookie": [], - "body": "{\n \"headers\": {\n \"x-forwarded-proto\": \"https\",\n \"x-forwarded-port\": \"443\",\n \"host\": \"postman-echo.com\",\n \"user-agent\": \"HttpRunner\",\n \"accept\": \"*/*\",\n \"accept-encoding\": \"gzip, deflate, br\"\n }\n}" + "body": "{\n \"headers\": {\n \"x-forwarded-proto\": \"https\",\n \"x-forwarded-port\": \"443\",\n \"host\": \"postman-echo.com\",\n \"user-agent\": \"HttpRunner\",\n \"cookie\": \"Cookie_1=c1; Cookie_2=c2; sails.sid=s%3AGX6aS9b_phvUSUk66w7ZBgWuOPI7IIKT.ayEGTaW4U35eAWyPz%2Fh6Q74DonNcbqw3H5Q5Zv%2BfKMY\",\n \"accept\": \"*/*\",\n \"accept-encoding\": \"gzip, deflate, br\"\n }\n}" } ] } diff --git a/examples/data/postman2case/patch.yml b/examples/data/postman2case/patch.yml new file mode 100644 index 00000000..c657b5ef --- /dev/null +++ b/examples/data/postman2case/patch.yml @@ -0,0 +1,4 @@ +headers: + User-Agent: "this header will be created or updated" +cookies: + Cookie1: "this cookie will be created or updated" diff --git a/examples/data/postman2case/profile.yml b/examples/data/postman2case/profile.yml new file mode 100644 index 00000000..42e2e9f4 --- /dev/null +++ b/examples/data/postman2case/profile.yml @@ -0,0 +1,4 @@ +headers: + Header1: "all original headers will be overridden" +cookies: + Cookie1: "all original cookies will be overridden" \ No newline at end of file diff --git a/hrp/cmd/convert.go b/hrp/cmd/convert.go index 0247d147..48a9f4bc 100644 --- a/hrp/cmd/convert.go +++ b/hrp/cmd/convert.go @@ -7,7 +7,7 @@ import ( "github.com/rs/zerolog/log" "github.com/spf13/cobra" - "github.com/httprunner/httprunner/v4/hrp/internal/convert" + "github.com/httprunner/httprunner/v4/hrp/internal/convert/case2script" ) var convertCmd = &cobra.Command{ @@ -18,15 +18,16 @@ var convertCmd = &cobra.Command{ setLogLevel(logLevel) }, RunE: func(cmd *cobra.Command, args []string) error { + // TODO: integrate har2case, postman2case, etc. in convert command (forward compatibility) if !pytestFlag && !gotestFlag { return errors.New("please specify convertion type") } var err error if gotestFlag { - err = convert.Convert2TestScripts("gotest", args...) + err = case2script.Convert2TestScripts("gotest", args...) } else { - err = convert.Convert2TestScripts("pytest", args...) + err = case2script.Convert2TestScripts("pytest", args...) } if err != nil { log.Error().Err(err).Msg("convert test scripts failed") diff --git a/hrp/cmd/har2case.go b/hrp/cmd/har2case.go index eecd40cc..42fab1bd 100644 --- a/hrp/cmd/har2case.go +++ b/hrp/cmd/har2case.go @@ -6,7 +6,7 @@ import ( "github.com/rs/zerolog/log" "github.com/spf13/cobra" - "github.com/httprunner/httprunner/v4/hrp/internal/har2case" + "github.com/httprunner/httprunner/v4/hrp/internal/convert/har2case" ) // har2caseCmd represents the har2case command @@ -22,7 +22,7 @@ var har2caseCmd = &cobra.Command{ var outputFiles []string for _, arg := range args { // must choose one - if !genYAMLFlag && !genJSONFlag { + if !har2caseGenYAMLFlag && !har2caseGenJSONFlag { return errors.New("please select convert format type") } var outputPath string @@ -31,17 +31,22 @@ var har2caseCmd = &cobra.Command{ har := har2case.NewHAR(arg) // specify output dir - if outputDir != "" { - har.SetOutputDir(outputDir) + if har2caseOutputDir != "" { + har.SetOutputDir(har2caseOutputDir) } // specify profile - if profilePath != "" { - har.SetProfile(profilePath) + if har2caseProfilePath != "" { + har.SetProfile(har2caseProfilePath) + } + + // specify profile + if har2casePatchPath != "" { + har.SetPatch(har2casePatchPath) } // generate json/yaml files - if genYAMLFlag { + if har2caseGenYAMLFlag { outputPath, err = har.GenYAML() } else { outputPath, err = har.GenJSON() // default @@ -57,16 +62,18 @@ var har2caseCmd = &cobra.Command{ } var ( - genJSONFlag bool - genYAMLFlag bool - outputDir string - profilePath string + har2caseGenJSONFlag bool + har2caseGenYAMLFlag bool + har2caseOutputDir string + har2caseProfilePath string + har2casePatchPath string ) func init() { rootCmd.AddCommand(har2caseCmd) - har2caseCmd.Flags().BoolVarP(&genJSONFlag, "to-json", "j", true, "convert to JSON format") - har2caseCmd.Flags().BoolVarP(&genYAMLFlag, "to-yaml", "y", false, "convert to YAML format") - har2caseCmd.Flags().StringVarP(&outputDir, "output-dir", "d", "", "specify output directory, default to the same dir with har file") - har2caseCmd.Flags().StringVarP(&profilePath, "profile", "p", "", "specify profile path to override headers and cookies") + har2caseCmd.Flags().BoolVarP(&har2caseGenJSONFlag, "to-json", "j", true, "convert to JSON format") + har2caseCmd.Flags().BoolVarP(&har2caseGenYAMLFlag, "to-yaml", "y", false, "convert to YAML format") + har2caseCmd.Flags().StringVarP(&har2caseOutputDir, "output-dir", "d", "", "specify output directory, default to the same dir with har file") + har2caseCmd.Flags().StringVarP(&har2caseProfilePath, "profile", "p", "", "specify profile path to override headers and cookies") + har2caseCmd.Flags().StringVarP(&har2casePatchPath, "patch", "r", "", "specify the path of the file used to replace headers and cookies") } diff --git a/hrp/cmd/postman2case.go b/hrp/cmd/postman2case.go index 5ccedacb..2e0c1369 100644 --- a/hrp/cmd/postman2case.go +++ b/hrp/cmd/postman2case.go @@ -6,7 +6,7 @@ import ( "github.com/rs/zerolog/log" "github.com/spf13/cobra" - "github.com/httprunner/httprunner/v4/hrp/internal/postman2case" + "github.com/httprunner/httprunner/v4/hrp/internal/convert/postman2case" ) // postman2caseCmd represents the postman2case command @@ -22,24 +22,34 @@ var postman2caseCmd = &cobra.Command{ var outputFiles []string for _, arg := range args { // must choose one - if !postman2JSONFlag && !postman2YAMLFlag { + if !postman2caseGenJSONFlag && !postman2caseGenYAMLFlag { return errors.New("please select convert format type") } var outputPath string var err error - postman := postman2case.NewCollection(arg) + collection := postman2case.NewCollection(arg) // specify output dir - if postman2Dir != "" { - postman.SetOutputDir(postman2Dir) + if postman2caseOutputDir != "" { + collection.SetOutputDir(postman2caseOutputDir) + } + + // specify profile path + if postman2caseProfilePath != "" { + collection.SetProfile(postman2caseProfilePath) + } + + // specify patch path + if postman2casePatchPath != "" { + collection.SetPatch(postman2casePatchPath) } // generate json/yaml files - if genYAMLFlag { - outputPath, err = postman.GenYAML() + if postman2caseGenYAMLFlag { + outputPath, err = collection.GenYAML() } else { - outputPath, err = postman.GenJSON() // default + outputPath, err = collection.GenJSON() // default } if err != nil { return err @@ -52,14 +62,18 @@ var postman2caseCmd = &cobra.Command{ } var ( - postman2JSONFlag bool - postman2YAMLFlag bool - postman2Dir string + postman2caseGenJSONFlag bool + postman2caseGenYAMLFlag bool + postman2caseOutputDir string + postman2caseProfilePath string + postman2casePatchPath string ) func init() { rootCmd.AddCommand(postman2caseCmd) - postman2caseCmd.Flags().BoolVarP(&postman2JSONFlag, "to-json", "j", true, "convert to JSON format") - postman2caseCmd.Flags().BoolVarP(&postman2YAMLFlag, "to-yaml", "y", false, "convert to YAML format") - postman2caseCmd.Flags().StringVarP(&postman2Dir, "output-dir", "d", "", "specify output directory, default to the same dir with postman collection file") + postman2caseCmd.Flags().BoolVarP(&postman2caseGenJSONFlag, "to-json", "j", true, "convert to JSON format") + postman2caseCmd.Flags().BoolVarP(&postman2caseGenYAMLFlag, "to-yaml", "y", false, "convert to YAML format") + postman2caseCmd.Flags().StringVarP(&postman2caseOutputDir, "output-dir", "d", "", "specify output directory, default to the same dir with postman collection file") + postman2caseCmd.Flags().StringVarP(&postman2caseProfilePath, "profile", "p", "", "specify profile path to override original headers (except for Content-Type) and cookies") + postman2caseCmd.Flags().StringVarP(&postman2casePatchPath, "patch", "r", "", "specify patch path to create or update headers and cookies") } diff --git a/hrp/internal/convert/main.go b/hrp/internal/convert/case2script/main.go similarity index 99% rename from hrp/internal/convert/main.go rename to hrp/internal/convert/case2script/main.go index ea58dd6e..bfc75b27 100644 --- a/hrp/internal/convert/main.go +++ b/hrp/internal/convert/case2script/main.go @@ -1,4 +1,4 @@ -package convert +package case2script import ( _ "embed" diff --git a/hrp/internal/convert/testcase.tmpl b/hrp/internal/convert/case2script/testcase.tmpl similarity index 100% rename from hrp/internal/convert/testcase.tmpl rename to hrp/internal/convert/case2script/testcase.tmpl diff --git a/hrp/internal/har2case/README.md b/hrp/internal/convert/har2case/README.md similarity index 100% rename from hrp/internal/har2case/README.md rename to hrp/internal/convert/har2case/README.md diff --git a/hrp/internal/har2case/core.go b/hrp/internal/convert/har2case/core.go similarity index 84% rename from hrp/internal/har2case/core.go rename to hrp/internal/convert/har2case/core.go index 25824855..0e96a96d 100644 --- a/hrp/internal/har2case/core.go +++ b/hrp/internal/convert/har2case/core.go @@ -22,6 +22,13 @@ const ( suffixYAML = ".yaml" ) +const ( + configProfile = "profile" + configPatch = "patch" + keyHeaders = "headers" + keyCookies = "cookies" +) + func NewHAR(path string) *har { return &har{ path: path, @@ -33,6 +40,7 @@ type har struct { filterStr string excludeStr string profile map[string]interface{} + patch map[string]interface{} outputDir string } @@ -46,6 +54,16 @@ func (h *har) SetProfile(path string) { } } +func (h *har) SetPatch(path string) { + log.Info().Str("path", path).Msg("set patch") + h.patch = make(map[string]interface{}) + err := builtin.LoadFile(path, h.patch) + if err != nil { + log.Warn().Str("path", path). + Msg("invalid patch format, ignore!") + } +} + func (h *har) SetOutputDir(dir string) { log.Info().Str("dir", dir).Msg("set output directory") h.outputDir = dir @@ -146,6 +164,7 @@ func (h *har) prepareTestStep(entry *Entry) (*hrp.TStep, error) { Validators: make([]interface{}, 0), }, profile: h.profile, + patch: h.patch, } if err := step.makeRequestMethod(entry); err != nil { return nil, err @@ -174,6 +193,7 @@ func (h *har) prepareTestStep(entry *Entry) (*hrp.TStep, error) { type tStep struct { hrp.TStep profile map[string]interface{} + patch map[string]interface{} } func (s *tStep) makeRequestMethod(entry *Entry) error { @@ -199,43 +219,59 @@ func (s *tStep) makeRequestParams(entry *Entry) error { return nil } +func (s *tStep) updateRequestInfo(config string, key string) bool { + var m map[string]interface{} + switch config { + case configProfile: + m = s.profile + case configPatch: + m = s.patch + default: + return false + } + iRequestMap, existed := m[key] + if existed { + requestMap, ok := iRequestMap.(map[string]interface{}) + if ok { + for k, v := range requestMap { + switch key { + case keyHeaders: + s.Request.Headers[k] = fmt.Sprintf("%v", v) + case keyCookies: + s.Request.Cookies[k] = fmt.Sprintf("%v", v) + } + } + return true + } + log.Warn().Interface(key, iRequestMap).Msgf("%v from %v is not a map, ignore!", key, config) + } + return false +} + func (s *tStep) makeRequestCookies(entry *Entry) error { s.Request.Cookies = make(map[string]string) - cookies, ok := s.profile["cookies"] - if ok { - // use cookies from profile - cookies, ok := cookies.(map[string]interface{}) - if ok { - for k, v := range cookies { - s.Request.Cookies[k] = fmt.Sprintf("%v", v) - } - return nil - } - log.Warn().Interface("cookies", cookies). - Msg("cookies from profile is not a map, ignore!") + + // override all cookies according to the profile + if s.updateRequestInfo(configProfile, keyCookies) { + return nil } // use cookies from har for _, cookie := range entry.Request.Cookies { s.Request.Cookies[cookie.Name] = cookie.Value } + + // create or update the cookies indicated in the patch + s.updateRequestInfo(configPatch, keyCookies) return nil } func (s *tStep) makeRequestHeaders(entry *Entry) error { s.Request.Headers = make(map[string]string) - headers, ok := s.profile["headers"] - if ok { - // use headers from profile - cookies, ok := headers.(map[string]interface{}) - if ok { - for k, v := range cookies { - s.Request.Headers[k] = fmt.Sprintf("%v", v) - } - return nil - } - log.Warn().Interface("headers", headers). - Msg("headers from profile is not a map, ignore!") + + // override all headers according to the profile + if s.updateRequestInfo(configProfile, keyHeaders) { + return nil } // use headers from har @@ -245,6 +281,9 @@ func (s *tStep) makeRequestHeaders(entry *Entry) error { } s.Request.Headers[header.Name] = header.Value } + + // create or update the headers indicated in the patch + s.updateRequestInfo(configPatch, keyHeaders) return nil } diff --git a/hrp/internal/har2case/core_test.go b/hrp/internal/convert/har2case/core_test.go similarity index 90% rename from hrp/internal/har2case/core_test.go rename to hrp/internal/convert/har2case/core_test.go index de2ee910..ce6466fe 100644 --- a/hrp/internal/har2case/core_test.go +++ b/hrp/internal/convert/har2case/core_test.go @@ -1,6 +1,7 @@ package har2case import ( + "fmt" "testing" "github.com/stretchr/testify/assert" @@ -9,9 +10,9 @@ import ( ) var ( - harPath = "../../../examples/data/har/demo.har" - harPath2 = "../../../examples/data/har/postman-echo.har" - profilePath = "../../../examples/data/har/profile.yml" + harPath = "../../../../examples/data/har/demo.har" + harPath2 = "../../../../examples/data/har/postman-echo.har" + profilePath = "../../../../examples/data/har/profile.yml" ) func TestGenJSON(t *testing.T) { @@ -381,3 +382,32 @@ func TestMakeValidate(t *testing.T) { t.Fatal() } } + +func Test_tStep_makeRequestCookies(t *testing.T) { + type fields struct { + TStep hrp.TStep + profile map[string]interface{} + patch map[string]interface{} + } + type args struct { + entry *Entry + } + tests := []struct { + name string + fields fields + args args + wantErr assert.ErrorAssertionFunc + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + s := &tStep{ + TStep: tt.fields.TStep, + profile: tt.fields.profile, + patch: tt.fields.patch, + } + tt.wantErr(t, s.makeRequestCookies(tt.args.entry), fmt.Sprintf("makeRequestCookies(%v)", tt.args.entry)) + }) + } +} diff --git a/hrp/internal/har2case/har.go b/hrp/internal/convert/har2case/har.go similarity index 100% rename from hrp/internal/har2case/har.go rename to hrp/internal/convert/har2case/har.go diff --git a/hrp/internal/postman2case/collection.go b/hrp/internal/convert/postman2case/collection.go similarity index 100% rename from hrp/internal/postman2case/collection.go rename to hrp/internal/convert/postman2case/collection.go diff --git a/hrp/internal/postman2case/core.go b/hrp/internal/convert/postman2case/core.go similarity index 72% rename from hrp/internal/postman2case/core.go rename to hrp/internal/convert/postman2case/core.go index f4b8b4e3..1f15cbf5 100644 --- a/hrp/internal/postman2case/core.go +++ b/hrp/internal/convert/postman2case/core.go @@ -3,7 +3,6 @@ package postman2case import ( "bytes" "fmt" - "github.com/httprunner/httprunner/v4/hrp/internal/json" "io" "mime/multipart" "net/url" @@ -17,6 +16,7 @@ import ( "github.com/httprunner/httprunner/v4/hrp" "github.com/httprunner/httprunner/v4/hrp/internal/builtin" + "github.com/httprunner/httprunner/v4/hrp/internal/json" ) const ( @@ -33,11 +33,18 @@ const ( ) const ( - suffixName = ".converted" + suffixName = ".converted" // distinguish the converted json(testcase) from the origin json(collection) extensionJSON = ".json" extensionYAML = ".yaml" ) +const ( + configProfile = "profile" + configPatch = "patch" + keyHeaders = "headers" + keyCookies = "cookies" +) + var contentTypeMap = map[string]string{ "text": "text/plain", "javascript": "application/javascript", @@ -54,9 +61,31 @@ func NewCollection(path string) *collection { type collection struct { path string + profile map[string]interface{} + patch map[string]interface{} outputDir string } +func (c *collection) SetProfile(path string) { + log.Info().Str("path", path).Msg("set profile") + c.profile = make(map[string]interface{}) + err := builtin.LoadFile(path, c.profile) + if err != nil { + log.Warn().Str("path", path). + Msg("invalid profile format, ignore!") + } +} + +func (c *collection) SetPatch(path string) { + log.Info().Str("path", path).Msg("set patch") + c.patch = make(map[string]interface{}) + err := builtin.LoadFile(path, c.patch) + if err != nil { + log.Warn().Str("path", path). + Msg("invalid patch format, ignore!") + } +} + func (c *collection) SetOutputDir(dir string) { log.Info().Str("dir", dir).Msg("set output directory") c.outputDir = dir @@ -169,10 +198,12 @@ func (c *collection) prepareTestStep(item *TItem) (*hrp.TStep, error) { Msg("convert teststep") step := &tStep{ - hrp.TStep{ + TStep: hrp.TStep{ Request: &hrp.Request{}, Validators: make([]interface{}, 0), }, + profile: c.profile, + patch: c.patch, } if err := step.makeRequestName(item); err != nil { return nil, err @@ -186,20 +217,22 @@ func (c *collection) prepareTestStep(item *TItem) (*hrp.TStep, error) { if err := step.makeRequestParams(item); err != nil { return nil, err } - if err := step.makeRequestHeadersAndCookies(item); err != nil { + if err := step.makeRequestHeaders(item); err != nil { + return nil, err + } + if err := step.makeRequestCookies(item); err != nil { return nil, err } if err := step.makeRequestBody(item); err != nil { return nil, err } - if err := step.makeValidate(item); err != nil { - return nil, err - } return &step.TStep, nil } type tStep struct { hrp.TStep + profile map[string]interface{} + patch map[string]interface{} } // makeRequestName indicates the step name the same as item name @@ -239,21 +272,89 @@ func (s *tStep) makeRequestParams(item *TItem) error { return nil } -func (s *tStep) makeRequestHeadersAndCookies(item *TItem) error { - s.Request.Headers = make(map[string]string) - for _, field := range item.Request.Headers { - if field.Disabled { - continue +func (s *tStep) updateRequestInfo(config string, key string) bool { + var m map[string]interface{} + switch config { + case configProfile: + m = s.profile + case configPatch: + m = s.patch + default: + return false + } + iRequestMap, existed := m[key] + if existed { + requestMap, ok := iRequestMap.(map[string]interface{}) + if ok { + for k, v := range requestMap { + switch key { + case keyHeaders: + s.Request.Headers[k] = fmt.Sprintf("%v", v) + case keyCookies: + s.Request.Cookies[k] = fmt.Sprintf("%v", v) + } + } + return true } - if strings.EqualFold(field.Key, "cookie") { - s.Request.Cookies[field.Key] = field.Value + log.Warn().Interface(key, iRequestMap).Msgf("%v from %v is not a map, ignore!", key, config) + } + return false +} + +func (s *tStep) makeRequestHeaders(item *TItem) error { + s.Request.Headers = make(map[string]string) + + // override all headers according to the profile + if s.updateRequestInfo(configProfile, keyHeaders) { + return nil + } + + // headers defined in postman collection + for _, field := range item.Request.Headers { + if field.Disabled || strings.EqualFold(field.Key, "cookie") { continue } s.Request.Headers[field.Key] = field.Value } + + // create or update the headers indicated in the patch + s.updateRequestInfo(configPatch, keyHeaders) return nil } +func (s *tStep) makeRequestCookies(item *TItem) error { + s.Request.Cookies = make(map[string]string) + + // override all cookies according to the profile + if s.updateRequestInfo(configProfile, keyCookies) { + return nil + } + + // cookies defined in postman collection + for _, field := range item.Request.Headers { + if field.Disabled || !strings.EqualFold(field.Key, "cookie") { + continue + } + s.parseRequestCookiesMap(field.Value) + } + + // create or update the cookies indicated in the patch + s.updateRequestInfo(configPatch, keyCookies) + return nil +} + +func (s *tStep) parseRequestCookiesMap(cookies string) { + for _, cookie := range strings.Split(cookies, ";") { + cookie = strings.TrimSpace(cookie) + index := strings.Index(cookie, "=") + if index == -1 { + log.Warn().Str("cookie", cookie).Msg("cookie format invalid") + continue + } + s.Request.Cookies[cookie[0:index]] = cookie[index+1:] + } +} + func (s *tStep) makeRequestBody(item *TItem) error { mode := item.Request.Body.Mode if mode == "" { @@ -267,7 +368,7 @@ func (s *tStep) makeRequestBody(item *TItem) error { case enumBodyUrlEncoded: return s.makeRequestBodyUrlEncoded(item) case enumBodyFile, enumBodyGraphQL: - return errors.New("not supported body type") + return errors.Errorf("unsupported body type: %v", mode) } return nil } diff --git a/hrp/internal/convert/postman2case/core_test.go b/hrp/internal/convert/postman2case/core_test.go new file mode 100644 index 00000000..a102e136 --- /dev/null +++ b/hrp/internal/convert/postman2case/core_test.go @@ -0,0 +1,155 @@ +package postman2case + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +var ( + collectionPath = "../../../../examples/data/postman2case/demo.json" + profilePath = "../../../../examples/data/postman2case/profile.yml" + patchPath = "../../../../examples/data/postman2case/patch.yml" +) + +func TestGenJSON(t *testing.T) { + jsonPath, err := NewCollection(collectionPath).GenJSON() + if !assert.NoError(t, err) { + t.Fatal() + } + if !assert.NotEmpty(t, jsonPath) { + t.Fatal() + } +} + +func TestGenYAML(t *testing.T) { + yamlPath, err := NewCollection(collectionPath).GenYAML() + if !assert.NoError(t, err) { + t.Fatal() + } + if !assert.NotEmpty(t, yamlPath) { + t.Fatal() + } +} + +func TestLoadCollection(t *testing.T) { + tCollection, err := NewCollection(collectionPath).load() + if !assert.NoError(t, err) { + t.Fatal(err) + } + if !assert.Equal(t, "postman collection demo", tCollection.Info.Name) { + t.Fatal() + } +} + +func TestMakeTestCase(t *testing.T) { + tCase, err := NewCollection(collectionPath).makeTestCase() + if !assert.NoError(t, err) { + t.Fatal() + } + // check name + if !assert.Equal(t, "postman collection demo", tCase.Config.Name) { + t.Fatal() + } + // check method + if !assert.EqualValues(t, "GET", tCase.TestSteps[0].Request.Method) { + t.Fatal() + } + if !assert.EqualValues(t, "POST", tCase.TestSteps[1].Request.Method) { + t.Fatal() + } + // check url + if !assert.Equal(t, "https://postman-echo.com/get", tCase.TestSteps[0].Request.URL) { + t.Fatal() + } + if !assert.Equal(t, "https://postman-echo.com/post", tCase.TestSteps[1].Request.URL) { + t.Fatal() + } + // check params + if !assert.Equal(t, "v1", tCase.TestSteps[0].Request.Params["k1"]) { + t.Fatal() + } + // check cookies (pass, postman collection doesn't contains cookies) + // check headers + if !assert.Contains(t, tCase.TestSteps[1].Request.Headers["Content-Type"], "multipart/form-data") { + t.Fatal() + } + if !assert.Equal(t, "application/x-www-form-urlencoded", tCase.TestSteps[2].Request.Headers["Content-Type"]) { + t.Fatal() + } + if !assert.Equal(t, "application/json", tCase.TestSteps[3].Request.Headers["Content-Type"]) { + t.Fatal() + } + if !assert.Equal(t, "text/plain", tCase.TestSteps[4].Request.Headers["Content-Type"]) { + t.Fatal() + } + if !assert.Equal(t, "HttpRunner", tCase.TestSteps[5].Request.Headers["User-Agent"]) { + t.Fatal() + } + // check body + if !assert.Equal(t, nil, tCase.TestSteps[0].Request.Body) { + t.Fatal() + } + if !assert.NotEmpty(t, tCase.TestSteps[1].Request.Body) { + t.Fatal() + } + if !assert.Equal(t, map[string]string{"k1": "v1", "k2": "v2"}, tCase.TestSteps[2].Request.Body) { + t.Fatal() + } + if !assert.Equal(t, map[string]interface{}{"k1": "v1", "k2": "v2"}, tCase.TestSteps[3].Request.Body) { + t.Fatal() + } + if !assert.Equal(t, "have a nice day", tCase.TestSteps[4].Request.Body) { + t.Fatal() + } + if !assert.Equal(t, nil, tCase.TestSteps[5].Request.Body) { + t.Fatal() + } +} + +func TestMakeTestCaseWithProfile(t *testing.T) { + c := NewCollection(collectionPath) + c.SetProfile(profilePath) + tCase, err := c.makeTestCase() + if !assert.NoError(t, err) { + t.Fatal() + } + for _, step := range tCase.TestSteps { + if step.Request.Method == "GET" && !assert.Len(t, step.Request.Headers, 1) { + t.Fatal() + } + if step.Request.Method == "POST" && !assert.Len(t, step.Request.Headers, 2) { + t.Fatal() + } + if !assert.Equal(t, "all original headers will be overridden", step.Request.Headers["Header1"]) { + t.Fatal() + } + if !assert.Len(t, step.Request.Cookies, 1) { + t.Fatal() + } + if !assert.Equal(t, "all original cookies will be overridden", step.Request.Cookies["Cookie1"]) { + t.Fatal() + } + } +} + +func TestMakeTestCaseWithPatch(t *testing.T) { + c := NewCollection(collectionPath) + c.SetPatch(patchPath) + tCase, err := c.makeTestCase() + if !assert.NoError(t, err) { + t.Fatal() + } + // create cookies Cookie1 indicated in patch + if !assert.Equal(t, "this cookie will be created or updated", tCase.TestSteps[0].Request.Cookies["Cookie1"]) { + t.Fatal() + } + // update header User-Agent indicated in patch + if !assert.Equal(t, "this header will be created or updated", tCase.TestSteps[5].Request.Headers["User-Agent"]) { + t.Fatal() + } + // pass header Connection which is not indicated in patch + if !assert.Equal(t, "close", tCase.TestSteps[5].Request.Headers["Connection"]) { + t.Fatal() + } +} diff --git a/hrp/internal/postman2case/core_test.go b/hrp/internal/postman2case/core_test.go deleted file mode 100644 index 47b9eabc..00000000 --- a/hrp/internal/postman2case/core_test.go +++ /dev/null @@ -1,39 +0,0 @@ -package postman2case - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -var collectionPath = "../../../examples/data/postman2case/postman_collection.json" - -func TestLoadPostmanCollection(t *testing.T) { - c, err := NewCollection(collectionPath).load() - if !assert.NoError(t, err) { - t.Fatal(err) - } - if !assert.Equal(t, "postman collection demo", c.Info.Name) { - t.Fatal() - } -} - -func TestGenJSON(t *testing.T) { - jsonPath, err := NewCollection(collectionPath).GenJSON() - if !assert.NoError(t, err) { - t.Fatal() - } - if !assert.NotEmpty(t, jsonPath) { - t.Fatal() - } -} - -func TestGenYAML(t *testing.T) { - yamlPath, err := NewCollection(collectionPath).GenYAML() - if !assert.NoError(t, err) { - t.Fatal() - } - if !assert.NotEmpty(t, yamlPath) { - t.Fatal() - } -} From 39413900f17ae57073674341b34b06db8eb6008a Mon Sep 17 00:00:00 2001 From: debugtalk <mail@debugtalk.com> Date: Thu, 19 May 2022 18:21:21 +0800 Subject: [PATCH 040/109] change: upgrade funplugin to v0.4.6 --- go.mod | 2 +- go.sum | 4 ++-- hrp/internal/httpstat/main.go | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 5dc2859b..34901ad6 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/getsentry/sentry-go v0.13.0 github.com/google/uuid v1.3.0 github.com/gorilla/websocket v1.4.1 - github.com/httprunner/funplugin v0.4.5 + github.com/httprunner/funplugin v0.4.6 github.com/jinzhu/copier v0.3.2 github.com/jmespath/go-jmespath v0.4.0 github.com/json-iterator/go v1.1.12 diff --git a/go.sum b/go.sum index 62502254..a3bc4a71 100644 --- a/go.sum +++ b/go.sum @@ -242,8 +242,8 @@ github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2p github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb h1:b5rjCoWHc7eqmAS4/qyk21ZsHyb6Mxv/jykxvNTkU4M= github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= -github.com/httprunner/funplugin v0.4.5 h1:2KCj5AZZA22OER6TN5P/PSBYFMiKpgTmCRbDmHB1tos= -github.com/httprunner/funplugin v0.4.5/go.mod h1:vPyeJIfbpGe0epZZtAV0wCn16gLY9+imSw/zfxq0Lcc= +github.com/httprunner/funplugin v0.4.6 h1:wwpjzo3G9a5BCXBkHs845w4ifKaCtVa/yQjREQjQOgo= +github.com/httprunner/funplugin v0.4.6/go.mod h1:vPyeJIfbpGe0epZZtAV0wCn16gLY9+imSw/zfxq0Lcc= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/imkira/go-interpol v1.1.0/go.mod h1:z0h2/2T3XF8kyEPpRgJ3kmNv+C43p+I/CoI+jC3w2iA= diff --git a/hrp/internal/httpstat/main.go b/hrp/internal/httpstat/main.go index 29bf464d..49242193 100644 --- a/hrp/internal/httpstat/main.go +++ b/hrp/internal/httpstat/main.go @@ -39,11 +39,11 @@ const ( ) func fmta(d time.Duration) string { - return color.BlueString("%7dms", int(d.Milliseconds())) + return color.YellowString("%7dms", int(d.Milliseconds())) } func fmtb(d time.Duration) string { - return color.MagentaString("%-9s", strconv.Itoa(int(d.Milliseconds()))+"ms") + return color.RedString("%-9s", strconv.Itoa(int(d.Milliseconds()))+"ms") } func grayscale(code color.Attribute) func(string, ...interface{}) string { @@ -137,7 +137,7 @@ func (s *Stat) Print() { if s.network != "" && s.addr != "" { printf("\n%s %s: %s\n", color.CyanString("Connected to"), - color.YellowString(s.network), + color.MagentaString(s.network), color.BlueString(s.addr), ) } From f02e87d7544369be3359ad0820ec32fbc75fa376 Mon Sep 17 00:00:00 2001 From: debugtalk <mail@debugtalk.com> Date: Thu, 19 May 2022 18:27:04 +0800 Subject: [PATCH 041/109] change: remove trigger for v4.1-dev --- .github/workflows/hrp-scaffold.yml | 1 - .github/workflows/smoketest.yml | 1 - .github/workflows/unittest.yml | 1 - 3 files changed, 3 deletions(-) diff --git a/.github/workflows/hrp-scaffold.yml b/.github/workflows/hrp-scaffold.yml index 726cc773..0d96ee00 100644 --- a/.github/workflows/hrp-scaffold.yml +++ b/.github/workflows/hrp-scaffold.yml @@ -6,7 +6,6 @@ on: - master - v2 - v3 - - v4.1-dev pull_request: env: diff --git a/.github/workflows/smoketest.yml b/.github/workflows/smoketest.yml index 276deea4..f914858a 100644 --- a/.github/workflows/smoketest.yml +++ b/.github/workflows/smoketest.yml @@ -6,7 +6,6 @@ on: - master - v2 - v3 - - v4.1-dev pull_request: env: diff --git a/.github/workflows/unittest.yml b/.github/workflows/unittest.yml index 793edc30..77e221fc 100644 --- a/.github/workflows/unittest.yml +++ b/.github/workflows/unittest.yml @@ -6,7 +6,6 @@ on: - master - v2 - v3 - - v4.1-dev pull_request: env: From 480f895f9f68e7f8e5d989aad79a636c6e201e43 Mon Sep 17 00:00:00 2001 From: debugtalk <mail@debugtalk.com> Date: Thu, 19 May 2022 18:27:26 +0800 Subject: [PATCH 042/109] update changelog --- docs/CHANGELOG.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index b3dc23dd..e93ec00d 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -1,13 +1,17 @@ # Release History -## v4.1.0-alpha (2022-05-09) +## v4.1.0-beta (2022-05-19) - feat: add pre-commit-hook to format go/python code **go version** +- feat: add boomer mode(standalone/master/worker) - fix: step request elapsed timing should contain ContentTransfer part - fix #1288: unable to go get httprunner v4 +- fix: panic when config didn't exist in testcase file +- fix: disable keep alive and improve RPS accuracy +- fix: improve RPS accuracy **python version** From 8712419a64f32fcceef0076866f57ba036a737c6 Mon Sep 17 00:00:00 2001 From: debugtalk <mail@debugtalk.com> Date: Thu, 19 May 2022 18:29:54 +0800 Subject: [PATCH 043/109] bump version to v4.1.0-beta --- hrp/internal/version/VERSION | 2 +- httprunner/__init__.py | 2 +- pyproject.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/hrp/internal/version/VERSION b/hrp/internal/version/VERSION index 3a670224..9453e976 100644 --- a/hrp/internal/version/VERSION +++ b/hrp/internal/version/VERSION @@ -1 +1 @@ -v4.1.0-alpha \ No newline at end of file +v4.1.0-beta \ No newline at end of file diff --git a/httprunner/__init__.py b/httprunner/__init__.py index 7adbd971..704aa8d5 100644 --- a/httprunner/__init__.py +++ b/httprunner/__init__.py @@ -1,4 +1,4 @@ -__version__ = "v4.1.0-alpha" +__version__ = "v4.1.0-beta" __description__ = "One-stop solution for HTTP(S) testing." diff --git a/pyproject.toml b/pyproject.toml index 408206d0..0be11d44 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "httprunner" -version = "v4.1.0-alpha" +version = "v4.1.0-beta" description = "One-stop solution for HTTP(S) testing." license = "Apache-2.0" readme = "README.md" From 05e2bed02fbcf412ec5236524d4b85930a73273c Mon Sep 17 00:00:00 2001 From: debugtalk <mail@debugtalk.com> Date: Thu, 19 May 2022 20:55:51 +0800 Subject: [PATCH 044/109] change: startproject demo --- .github/workflows/hrp-scaffold.yml | 14 +- examples/demo-with-go-plugin/plugin/go.mod | 2 +- examples/demo-with-go-plugin/plugin/go.sum | 4 +- .../{demo_with_funplugin.json => demo.json} | 0 ...demo_ref_testcase.yml => ref_testcase.yml} | 2 +- .../testcases/requests.json | 138 ++++++++++++++++++ .../{demo_requests.yml => requests.yml} | 0 .../{demo_with_funplugin.json => demo.json} | 0 ...demo_ref_testcase.yml => ref_testcase.yml} | 2 +- .../testcases/requests.json | 138 ++++++++++++++++++ .../{demo_requests.yml => requests.yml} | 0 ...o_without_funplugin.json => requests.json} | 0 hrp/internal/scaffold/main.go | 13 +- .../templates/testcases/demo_ref_testcase.yml | 2 +- .../testcases/demo_ref_testcase_test.py | 2 +- .../templates/testcases/demo_requests.json | 138 ++++++++++++++++++ .../templates/testcases/demo_requests_test.py | 4 +- hrp/runner_test.go | 4 +- 18 files changed, 441 insertions(+), 22 deletions(-) rename examples/demo-with-go-plugin/testcases/{demo_with_funplugin.json => demo.json} (100%) rename examples/demo-with-go-plugin/testcases/{demo_ref_testcase.yml => ref_testcase.yml} (95%) create mode 100644 examples/demo-with-go-plugin/testcases/requests.json rename examples/demo-with-go-plugin/testcases/{demo_requests.yml => requests.yml} (100%) rename examples/demo-with-py-plugin/testcases/{demo_with_funplugin.json => demo.json} (100%) rename examples/demo-with-py-plugin/testcases/{demo_ref_testcase.yml => ref_testcase.yml} (95%) create mode 100644 examples/demo-with-py-plugin/testcases/requests.json rename examples/demo-with-py-plugin/testcases/{demo_requests.yml => requests.yml} (100%) rename examples/demo-without-plugin/testcases/{demo_without_funplugin.json => requests.json} (100%) create mode 100644 hrp/internal/scaffold/templates/testcases/demo_requests.json diff --git a/.github/workflows/hrp-scaffold.yml b/.github/workflows/hrp-scaffold.yml index 0d96ee00..08d2ee46 100644 --- a/.github/workflows/hrp-scaffold.yml +++ b/.github/workflows/hrp-scaffold.yml @@ -34,10 +34,10 @@ jobs: - name: Run generated demo tests run: ./output/hrp run demo/testcases/ - name: Run API test demo in examples - run: ./output/hrp run examples/demo-with-py-plugin/testcases/demo_with_funplugin.json + run: ./output/hrp run examples/demo-with-py-plugin/testcases/demo.json - name: Run load test demo in examples run: | - ./output/hrp boom examples/demo-with-py-plugin/testcases/demo_with_funplugin.json --spawn-count 10 --spawn-rate 10 --loop-count 10 + ./output/hrp boom examples/demo-with-py-plugin/testcases/demo.json --spawn-count 10 --spawn-rate 10 --loop-count 10 scaffold-with-go-plugin: strategy: @@ -63,11 +63,11 @@ jobs: - name: Run API test demo in examples run: | go build -o examples/demo-with-go-plugin/debugtalk.bin examples/demo-with-go-plugin/plugin/debugtalk.go - ./output/hrp run examples/demo-with-go-plugin/testcases/demo_with_funplugin.json + ./output/hrp run examples/demo-with-go-plugin/testcases/demo.json - name: Run load test demo in examples run: | go build -o examples/demo-with-go-plugin/debugtalk.bin examples/demo-with-go-plugin/plugin/debugtalk.go - ./output/hrp boom examples/demo-with-go-plugin/testcases/demo_with_funplugin.json --spawn-count 10 --spawn-rate 10 --loop-count 10 + ./output/hrp boom examples/demo-with-go-plugin/testcases/demo.json --spawn-count 10 --spawn-rate 10 --loop-count 10 scaffold-without-custom-plugin: strategy: @@ -89,9 +89,9 @@ jobs: - name: Run start project run: ./output/hrp startproject demo --ignore-plugin - name: Run generated demo tests - run: ./output/hrp run demo/testcases/demo_without_funplugin.json + run: ./output/hrp run demo/testcases/requests.json - name: Run API test demo in examples - run: ./output/hrp run examples/demo-without-plugin/testcases/demo_without_funplugin.json + run: ./output/hrp run examples/demo-without-plugin/testcases/requests.json - name: Run load test demo in examples run: | - ./output/hrp boom examples/demo-without-plugin/testcases/demo_without_funplugin.json --spawn-count 10 --spawn-rate 10 --loop-count 10 + ./output/hrp boom examples/demo-without-plugin/testcases/requests.json --spawn-count 10 --spawn-rate 10 --loop-count 10 diff --git a/examples/demo-with-go-plugin/plugin/go.mod b/examples/demo-with-go-plugin/plugin/go.mod index 8dabb414..08a135d0 100644 --- a/examples/demo-with-go-plugin/plugin/go.mod +++ b/examples/demo-with-go-plugin/plugin/go.mod @@ -2,4 +2,4 @@ module plugin go 1.16 -require github.com/httprunner/funplugin v0.4.5 // indirect +require github.com/httprunner/funplugin v0.4.6 // indirect diff --git a/examples/demo-with-go-plugin/plugin/go.sum b/examples/demo-with-go-plugin/plugin/go.sum index 33c01bfb..59ea6478 100644 --- a/examples/demo-with-go-plugin/plugin/go.sum +++ b/examples/demo-with-go-plugin/plugin/go.sum @@ -58,8 +58,8 @@ github.com/hashicorp/go-plugin v1.4.3 h1:DXmvivbWD5qdiBts9TpBC7BYL1Aia5sxbRgQB+v github.com/hashicorp/go-plugin v1.4.3/go.mod h1:5fGEH17QVwTTcR0zV7yhDPLLmFX9YSZ38b18Udy6vYQ= github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb h1:b5rjCoWHc7eqmAS4/qyk21ZsHyb6Mxv/jykxvNTkU4M= github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= -github.com/httprunner/funplugin v0.4.5 h1:2KCj5AZZA22OER6TN5P/PSBYFMiKpgTmCRbDmHB1tos= -github.com/httprunner/funplugin v0.4.5/go.mod h1:vPyeJIfbpGe0epZZtAV0wCn16gLY9+imSw/zfxq0Lcc= +github.com/httprunner/funplugin v0.4.6 h1:wwpjzo3G9a5BCXBkHs845w4ifKaCtVa/yQjREQjQOgo= +github.com/httprunner/funplugin v0.4.6/go.mod h1:vPyeJIfbpGe0epZZtAV0wCn16gLY9+imSw/zfxq0Lcc= github.com/jhump/protoreflect v1.6.0/go.mod h1:eaTn3RZAmMBcV0fifFvlm6VHNz3wSkYyXYWUh7ymB74= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= diff --git a/examples/demo-with-go-plugin/testcases/demo_with_funplugin.json b/examples/demo-with-go-plugin/testcases/demo.json similarity index 100% rename from examples/demo-with-go-plugin/testcases/demo_with_funplugin.json rename to examples/demo-with-go-plugin/testcases/demo.json diff --git a/examples/demo-with-go-plugin/testcases/demo_ref_testcase.yml b/examples/demo-with-go-plugin/testcases/ref_testcase.yml similarity index 95% rename from examples/demo-with-go-plugin/testcases/demo_ref_testcase.yml rename to examples/demo-with-go-plugin/testcases/ref_testcase.yml index 0743488e..6cf32323 100644 --- a/examples/demo-with-go-plugin/testcases/demo_ref_testcase.yml +++ b/examples/demo-with-go-plugin/testcases/ref_testcase.yml @@ -13,7 +13,7 @@ teststeps: variables: foo1: testcase_ref_bar1 expect_foo1: testcase_ref_bar1 - testcase: testcases/demo_requests.yml + testcase: testcases/requests.yml export: - foo3 - diff --git a/examples/demo-with-go-plugin/testcases/requests.json b/examples/demo-with-go-plugin/testcases/requests.json new file mode 100644 index 00000000..b13f3837 --- /dev/null +++ b/examples/demo-with-go-plugin/testcases/requests.json @@ -0,0 +1,138 @@ +{ + "config": { + "name": "request methods testcase with functions", + "variables": { + "foo1": "config_bar1", + "foo2": "config_bar2", + "expect_foo1": "config_bar1", + "expect_foo2": "config_bar2" + }, + "base_url": "https://postman-echo.com", + "verify": false, + "export": [ + "foo3" + ] + }, + "teststeps": [ + { + "name": "get with params", + "variables": { + "foo1": "bar11", + "foo2": "bar21", + "sum_v": "${sum_two_int(1, 2)}" + }, + "request": { + "method": "GET", + "url": "/get", + "params": { + "foo1": "$foo1", + "foo2": "$foo2", + "sum_v": "$sum_v" + }, + "headers": { + "User-Agent": "funplugin/${get_version()}" + } + }, + "extract": { + "foo3": "body.args.foo2" + }, + "validate": [ + { + "eq": [ + "status_code", + 200 + ] + }, + { + "eq": [ + "body.args.foo1", + "bar11" + ] + }, + { + "eq": [ + "body.args.sum_v", + "3" + ] + }, + { + "eq": [ + "body.args.foo2", + "bar21" + ] + } + ] + }, + { + "name": "post raw text", + "variables": { + "foo1": "bar12", + "foo3": "bar32" + }, + "request": { + "method": "POST", + "url": "/post", + "headers": { + "User-Agent": "funplugin/${get_version()}", + "Content-Type": "text/plain" + }, + "data": "This is expected to be sent back as part of response body: $foo1-$foo2-$foo3." + }, + "validate": [ + { + "eq": [ + "status_code", + 200 + ] + }, + { + "eq": [ + "body.data", + "This is expected to be sent back as part of response body: bar12-$expect_foo2-bar32." + ] + } + ] + }, + { + "name": "post form data", + "variables": { + "foo2": "bar23" + }, + "request": { + "method": "POST", + "url": "/post", + "headers": { + "User-Agent": "funplugin/${get_version()}", + "Content-Type": "application/x-www-form-urlencoded" + }, + "data": "foo1=$foo1&foo2=$foo2&foo3=$foo3" + }, + "validate": [ + { + "eq": [ + "status_code", + 200 + ] + }, + { + "eq": [ + "body.form.foo1", + "$expect_foo1" + ] + }, + { + "eq": [ + "body.form.foo2", + "bar23" + ] + }, + { + "eq": [ + "body.form.foo3", + "bar21" + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/examples/demo-with-go-plugin/testcases/demo_requests.yml b/examples/demo-with-go-plugin/testcases/requests.yml similarity index 100% rename from examples/demo-with-go-plugin/testcases/demo_requests.yml rename to examples/demo-with-go-plugin/testcases/requests.yml diff --git a/examples/demo-with-py-plugin/testcases/demo_with_funplugin.json b/examples/demo-with-py-plugin/testcases/demo.json similarity index 100% rename from examples/demo-with-py-plugin/testcases/demo_with_funplugin.json rename to examples/demo-with-py-plugin/testcases/demo.json diff --git a/examples/demo-with-py-plugin/testcases/demo_ref_testcase.yml b/examples/demo-with-py-plugin/testcases/ref_testcase.yml similarity index 95% rename from examples/demo-with-py-plugin/testcases/demo_ref_testcase.yml rename to examples/demo-with-py-plugin/testcases/ref_testcase.yml index 0743488e..6cf32323 100644 --- a/examples/demo-with-py-plugin/testcases/demo_ref_testcase.yml +++ b/examples/demo-with-py-plugin/testcases/ref_testcase.yml @@ -13,7 +13,7 @@ teststeps: variables: foo1: testcase_ref_bar1 expect_foo1: testcase_ref_bar1 - testcase: testcases/demo_requests.yml + testcase: testcases/requests.yml export: - foo3 - diff --git a/examples/demo-with-py-plugin/testcases/requests.json b/examples/demo-with-py-plugin/testcases/requests.json new file mode 100644 index 00000000..b13f3837 --- /dev/null +++ b/examples/demo-with-py-plugin/testcases/requests.json @@ -0,0 +1,138 @@ +{ + "config": { + "name": "request methods testcase with functions", + "variables": { + "foo1": "config_bar1", + "foo2": "config_bar2", + "expect_foo1": "config_bar1", + "expect_foo2": "config_bar2" + }, + "base_url": "https://postman-echo.com", + "verify": false, + "export": [ + "foo3" + ] + }, + "teststeps": [ + { + "name": "get with params", + "variables": { + "foo1": "bar11", + "foo2": "bar21", + "sum_v": "${sum_two_int(1, 2)}" + }, + "request": { + "method": "GET", + "url": "/get", + "params": { + "foo1": "$foo1", + "foo2": "$foo2", + "sum_v": "$sum_v" + }, + "headers": { + "User-Agent": "funplugin/${get_version()}" + } + }, + "extract": { + "foo3": "body.args.foo2" + }, + "validate": [ + { + "eq": [ + "status_code", + 200 + ] + }, + { + "eq": [ + "body.args.foo1", + "bar11" + ] + }, + { + "eq": [ + "body.args.sum_v", + "3" + ] + }, + { + "eq": [ + "body.args.foo2", + "bar21" + ] + } + ] + }, + { + "name": "post raw text", + "variables": { + "foo1": "bar12", + "foo3": "bar32" + }, + "request": { + "method": "POST", + "url": "/post", + "headers": { + "User-Agent": "funplugin/${get_version()}", + "Content-Type": "text/plain" + }, + "data": "This is expected to be sent back as part of response body: $foo1-$foo2-$foo3." + }, + "validate": [ + { + "eq": [ + "status_code", + 200 + ] + }, + { + "eq": [ + "body.data", + "This is expected to be sent back as part of response body: bar12-$expect_foo2-bar32." + ] + } + ] + }, + { + "name": "post form data", + "variables": { + "foo2": "bar23" + }, + "request": { + "method": "POST", + "url": "/post", + "headers": { + "User-Agent": "funplugin/${get_version()}", + "Content-Type": "application/x-www-form-urlencoded" + }, + "data": "foo1=$foo1&foo2=$foo2&foo3=$foo3" + }, + "validate": [ + { + "eq": [ + "status_code", + 200 + ] + }, + { + "eq": [ + "body.form.foo1", + "$expect_foo1" + ] + }, + { + "eq": [ + "body.form.foo2", + "bar23" + ] + }, + { + "eq": [ + "body.form.foo3", + "bar21" + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/examples/demo-with-py-plugin/testcases/demo_requests.yml b/examples/demo-with-py-plugin/testcases/requests.yml similarity index 100% rename from examples/demo-with-py-plugin/testcases/demo_requests.yml rename to examples/demo-with-py-plugin/testcases/requests.yml diff --git a/examples/demo-without-plugin/testcases/demo_without_funplugin.json b/examples/demo-without-plugin/testcases/requests.json similarity index 100% rename from examples/demo-without-plugin/testcases/demo_without_funplugin.json rename to examples/demo-without-plugin/testcases/requests.json diff --git a/hrp/internal/scaffold/main.go b/hrp/internal/scaffold/main.go index c4824f8b..ca505d68 100644 --- a/hrp/internal/scaffold/main.go +++ b/hrp/internal/scaffold/main.go @@ -102,7 +102,7 @@ func CreateScaffold(projectName string, pluginType PluginType, force bool) error // create demo testcases if pluginType == Ignore { err := CopyFile("templates/testcases/demo_without_funplugin.json", - filepath.Join(projectName, "testcases", "demo_without_funplugin.json")) + filepath.Join(projectName, "testcases", "requests.json")) if err != nil { return err } @@ -111,17 +111,22 @@ func CreateScaffold(projectName string, pluginType PluginType, force bool) error } err = CopyFile("templates/testcases/demo_with_funplugin.json", - filepath.Join(projectName, "testcases", "demo_with_funplugin.json")) + filepath.Join(projectName, "testcases", "demo.json")) + if err != nil { + return err + } + err = CopyFile("templates/testcases/demo_requests.json", + filepath.Join(projectName, "testcases", "requests.json")) if err != nil { return err } err = CopyFile("templates/testcases/demo_requests.yml", - filepath.Join(projectName, "testcases", "demo_requests.yml")) + filepath.Join(projectName, "testcases", "requests.yml")) if err != nil { return err } err = CopyFile("templates/testcases/demo_ref_testcase.yml", - filepath.Join(projectName, "testcases", "demo_ref_testcase.yml")) + filepath.Join(projectName, "testcases", "ref_testcase.yml")) if err != nil { return err } diff --git a/hrp/internal/scaffold/templates/testcases/demo_ref_testcase.yml b/hrp/internal/scaffold/templates/testcases/demo_ref_testcase.yml index 0743488e..6cf32323 100644 --- a/hrp/internal/scaffold/templates/testcases/demo_ref_testcase.yml +++ b/hrp/internal/scaffold/templates/testcases/demo_ref_testcase.yml @@ -13,7 +13,7 @@ teststeps: variables: foo1: testcase_ref_bar1 expect_foo1: testcase_ref_bar1 - testcase: testcases/demo_requests.yml + testcase: testcases/requests.yml export: - foo3 - diff --git a/hrp/internal/scaffold/templates/testcases/demo_ref_testcase_test.py b/hrp/internal/scaffold/templates/testcases/demo_ref_testcase_test.py index 714030cd..ce77286e 100644 --- a/hrp/internal/scaffold/templates/testcases/demo_ref_testcase_test.py +++ b/hrp/internal/scaffold/templates/testcases/demo_ref_testcase_test.py @@ -1,5 +1,5 @@ # NOTE: Generated By HttpRunner v4.0.0 -# FROM: testcases/demo_ref_testcase.yml +# FROM: testcases/ref_testcase.yml import sys diff --git a/hrp/internal/scaffold/templates/testcases/demo_requests.json b/hrp/internal/scaffold/templates/testcases/demo_requests.json new file mode 100644 index 00000000..b13f3837 --- /dev/null +++ b/hrp/internal/scaffold/templates/testcases/demo_requests.json @@ -0,0 +1,138 @@ +{ + "config": { + "name": "request methods testcase with functions", + "variables": { + "foo1": "config_bar1", + "foo2": "config_bar2", + "expect_foo1": "config_bar1", + "expect_foo2": "config_bar2" + }, + "base_url": "https://postman-echo.com", + "verify": false, + "export": [ + "foo3" + ] + }, + "teststeps": [ + { + "name": "get with params", + "variables": { + "foo1": "bar11", + "foo2": "bar21", + "sum_v": "${sum_two_int(1, 2)}" + }, + "request": { + "method": "GET", + "url": "/get", + "params": { + "foo1": "$foo1", + "foo2": "$foo2", + "sum_v": "$sum_v" + }, + "headers": { + "User-Agent": "funplugin/${get_version()}" + } + }, + "extract": { + "foo3": "body.args.foo2" + }, + "validate": [ + { + "eq": [ + "status_code", + 200 + ] + }, + { + "eq": [ + "body.args.foo1", + "bar11" + ] + }, + { + "eq": [ + "body.args.sum_v", + "3" + ] + }, + { + "eq": [ + "body.args.foo2", + "bar21" + ] + } + ] + }, + { + "name": "post raw text", + "variables": { + "foo1": "bar12", + "foo3": "bar32" + }, + "request": { + "method": "POST", + "url": "/post", + "headers": { + "User-Agent": "funplugin/${get_version()}", + "Content-Type": "text/plain" + }, + "data": "This is expected to be sent back as part of response body: $foo1-$foo2-$foo3." + }, + "validate": [ + { + "eq": [ + "status_code", + 200 + ] + }, + { + "eq": [ + "body.data", + "This is expected to be sent back as part of response body: bar12-$expect_foo2-bar32." + ] + } + ] + }, + { + "name": "post form data", + "variables": { + "foo2": "bar23" + }, + "request": { + "method": "POST", + "url": "/post", + "headers": { + "User-Agent": "funplugin/${get_version()}", + "Content-Type": "application/x-www-form-urlencoded" + }, + "data": "foo1=$foo1&foo2=$foo2&foo3=$foo3" + }, + "validate": [ + { + "eq": [ + "status_code", + 200 + ] + }, + { + "eq": [ + "body.form.foo1", + "$expect_foo1" + ] + }, + { + "eq": [ + "body.form.foo2", + "bar23" + ] + }, + { + "eq": [ + "body.form.foo3", + "bar21" + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/hrp/internal/scaffold/templates/testcases/demo_requests_test.py b/hrp/internal/scaffold/templates/testcases/demo_requests_test.py index fc2ad5bb..6e05feb1 100644 --- a/hrp/internal/scaffold/templates/testcases/demo_requests_test.py +++ b/hrp/internal/scaffold/templates/testcases/demo_requests_test.py @@ -1,8 +1,8 @@ # NOTE: Generated By HttpRunner v4.0.0 -# FROM: testcases/demo_requests.yml +# FROM: testcases/requests.yml -from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCase +from httprunner import HttpRunner, Config, Step, RunRequest class TestCaseDemoRequests(HttpRunner): diff --git a/hrp/runner_test.go b/hrp/runner_test.go index 7ba1f4c2..2d743e6c 100644 --- a/hrp/runner_test.go +++ b/hrp/runner_test.go @@ -207,7 +207,7 @@ func TestLoadTestCases(t *testing.T) { if !assert.Nil(t, err) { t.Fatal() } - if !assert.Equal(t, len(testCases), 3) { + if !assert.Equal(t, len(testCases), 4) { t.Fatal() } @@ -217,7 +217,7 @@ func TestLoadTestCases(t *testing.T) { if !assert.Nil(t, err) { t.Fatal() } - if !assert.Equal(t, len(testCases), 3) { + if !assert.Equal(t, len(testCases), 4) { t.Fatal() } From a283565d41d8b6c5e6f734d7488cc230343c348d Mon Sep 17 00:00:00 2001 From: xucong053 <xucong053@outlook.com> Date: Thu, 19 May 2022 21:02:34 +0800 Subject: [PATCH 045/109] fix: modify the format of data-driven parameters setting --- examples/hrp/parameters_test.json | 10 ++++++++-- examples/hrp/parameters_test.yaml | 8 ++++++-- hrp/parameters.go | 21 +++++++++++++-------- hrp/parameters_test.go | 28 ++++++++++++++-------------- 4 files changed, 41 insertions(+), 26 deletions(-) diff --git a/examples/hrp/parameters_test.json b/examples/hrp/parameters_test.json index 84c36531..9944599e 100644 --- a/examples/hrp/parameters_test.json +++ b/examples/hrp/parameters_test.json @@ -10,8 +10,14 @@ }, "parameters_setting": { "strategies": { - "user_agent": "sequential", - "username-password": "random" + "user_agent": { + "name": "user-identity", + "pick_order": "sequential" + }, + "username-password": { + "name": "user-info", + "pick_order": "random" + } }, "limit": 6 }, diff --git a/examples/hrp/parameters_test.yaml b/examples/hrp/parameters_test.yaml index b5d06c71..aaaf9b41 100644 --- a/examples/hrp/parameters_test.yaml +++ b/examples/hrp/parameters_test.yaml @@ -5,8 +5,12 @@ config: username-password: ${parameterize($file)} parameters_setting: strategies: - user_agent: "sequential" - username-password: "random" + user_agent: + name: "user-identity" + pick_order: "sequential" + username-password: + name: "user-info" + pick_order: "random" limit: 6 variables: app_version: v1 diff --git a/hrp/parameters.go b/hrp/parameters.go index 5797f618..06007025 100644 --- a/hrp/parameters.go +++ b/hrp/parameters.go @@ -12,17 +12,17 @@ import ( ) type TParamsConfig struct { - Strategy iteratorStrategy `json:"strategy,omitempty" yaml:"strategy,omitempty"` // overall strategy + PickOrder iteratorPickOrder `json:"strategy,omitempty" yaml:"strategy,omitempty"` // overall pick-order strategy Strategies map[string]iteratorStrategy `json:"strategies,omitempty" yaml:"strategies,omitempty"` // individual strategies for each parameters Limit int `json:"limit,omitempty" yaml:"limit,omitempty"` } -type iteratorStrategy string +type iteratorPickOrder string const ( - strategySequential iteratorStrategy = "sequential" - strategyRandom iteratorStrategy = "random" - strategyUnique iteratorStrategy = "unique" + strategySequential iteratorPickOrder = "sequential" + strategyRandom iteratorPickOrder = "random" + strategyUnique iteratorPickOrder = "unique" ) /* @@ -33,6 +33,11 @@ const ( */ type Parameters []map[string]interface{} +type iteratorStrategy struct { + Name string `json:"name,omitempty" yaml:"name,omitempty"` + PickOrder iteratorPickOrder `json:"pick_order,omitempty" yaml:"pick_order,omitempty"` +} + func initParametersIterator(cfg *TConfig) (*ParametersIterator, error) { parameters, err := loadParameters(cfg.Parameters, cfg.Variables) if err != nil { @@ -65,12 +70,12 @@ func newParametersIterator(parameters map[string]Parameters, config *TParamsConf // check parameter individual strategy strategy, ok := config.Strategies[paramName] if !ok { - // default to overall strategy - strategy = config.Strategy + // default to overall pick order + strategy.PickOrder = config.PickOrder } // group parameters by strategy - if strategy == strategyRandom { + if strategy.PickOrder == strategyRandom { iterator.randomParameterNames = append(iterator.randomParameterNames, paramName) } else { parametersList = append(parametersList, parameters[paramName]) diff --git a/hrp/parameters_test.go b/hrp/parameters_test.go index 19bb9fae..02176415 100644 --- a/hrp/parameters_test.go +++ b/hrp/parameters_test.go @@ -137,25 +137,25 @@ func TestInitParametersIteratorCount(t *testing.T) { }, 6, // 3 * 2 * 1 }, - // default equals to set overall parameters strategy to "sequential" + // default equals to set overall parameters pick-order to "sequential" { &TConfig{ Parameters: configParameters, ParametersSetting: &TParamsConfig{ - Strategy: "sequential", + PickOrder: "sequential", }, }, 6, // 3 * 2 * 1 }, - // default equals to set each individual parameters strategy to "sequential" + // default equals to set each individual parameters pick-order to "sequential" { &TConfig{ Parameters: configParameters, ParametersSetting: &TParamsConfig{ Strategies: map[string]iteratorStrategy{ - "username-password": "sequential", - "user_agent": "sequential", - "app_version": "sequential", + "username-password": {Name: "user-info", PickOrder: "sequential"}, + "user_agent": {Name: "user-identity", PickOrder: "sequential"}, + "app_version": {Name: "app-version", PickOrder: "sequential"}, }, }, }, @@ -166,33 +166,33 @@ func TestInitParametersIteratorCount(t *testing.T) { Parameters: configParameters, ParametersSetting: &TParamsConfig{ Strategies: map[string]iteratorStrategy{ - "user_agent": "sequential", - "app_version": "sequential", + "user_agent": {Name: "user-identity", PickOrder: "sequential"}, + "app_version": {Name: "app-version", PickOrder: "sequential"}, }, }, }, 6, // 3 * 2 * 1 }, - // set overall parameters overall strategy to "random" + // set overall parameters overall pick-order to "random" // each random parameters only select one item { &TConfig{ Parameters: configParameters, ParametersSetting: &TParamsConfig{ - Strategy: "random", + PickOrder: "random", }, }, 1, // 1 * 1 * 1 }, - // set some individual parameters strategy to "random" + // set some individual parameters pick-order to "random" // this will override overall strategy { &TConfig{ Parameters: configParameters, ParametersSetting: &TParamsConfig{ Strategies: map[string]iteratorStrategy{ - "user_agent": "random", + "user_agent": {Name: "user-identity", PickOrder: "random"}, }, }, }, @@ -203,7 +203,7 @@ func TestInitParametersIteratorCount(t *testing.T) { Parameters: configParameters, ParametersSetting: &TParamsConfig{ Strategies: map[string]iteratorStrategy{ - "username-password": "random", + "username-password": {Name: "user-info", PickOrder: "random"}, }, }, }, @@ -349,7 +349,7 @@ func TestInitParametersIteratorContent(t *testing.T) { ParametersSetting: &TParamsConfig{ Limit: 5, // limit could also be greater than total Strategies: map[string]iteratorStrategy{ - "username-password": "random", + "username-password": {Name: "user-info", PickOrder: "random"}, }, }, }, From a25cae0aa93e88fe67caf61d827a92979f90f82b Mon Sep 17 00:00:00 2001 From: xucong053 <xucong053@outlook.com> Date: Thu, 19 May 2022 22:58:16 +0800 Subject: [PATCH 046/109] feat: support running load testing by loading profile --- hrp/cmd/boom.go | 94 ++++++++++++++++++++--------------- hrp/internal/scaffold/main.go | 19 ++++++- hrp/parameters.go | 14 +++--- hrp/plugin.go | 2 +- hrp/testcase.go | 2 +- 5 files changed, 82 insertions(+), 49 deletions(-) diff --git a/hrp/cmd/boom.go b/hrp/cmd/boom.go index 22d8782d..5ae038e1 100644 --- a/hrp/cmd/boom.go +++ b/hrp/cmd/boom.go @@ -1,12 +1,15 @@ package cmd import ( + "os" "time" + "github.com/rs/zerolog/log" "github.com/spf13/cobra" "github.com/httprunner/httprunner/v4/hrp" "github.com/httprunner/httprunner/v4/hrp/internal/boomer" + "github.com/httprunner/httprunner/v4/hrp/internal/builtin" ) // boomCmd represents the boom command @@ -28,57 +31,70 @@ var boomCmd = &cobra.Command{ path := hrp.TestCasePath(arg) paths = append(paths, &path) } - hrpBoomer := hrp.NewBoomer(spawnCount, spawnRate) - hrpBoomer.SetRateLimiter(maxRPS, requestIncreaseRate) - if loopCount > 0 { - hrpBoomer.SetLoopCount(loopCount) + // if set profile, the priority is higher than the other commands + if boomArgs.profile != "" { + err := builtin.LoadFile(boomArgs.profile, &boomArgs) + if err != nil { + log.Error().Err(err).Msg("failed to load profile") + os.Exit(1) + } } - if !disableConsoleOutput { + + hrpBoomer := hrp.NewBoomer(boomArgs.SpawnCount, boomArgs.SpawnRate) + hrpBoomer.SetRateLimiter(boomArgs.MaxRPS, boomArgs.RequestIncreaseRate) + if boomArgs.LoopCount > 0 { + hrpBoomer.SetLoopCount(boomArgs.LoopCount) + } + if !boomArgs.DisableConsoleOutput { hrpBoomer.AddOutput(boomer.NewConsoleOutput()) } - if prometheusPushgatewayURL != "" { - hrpBoomer.AddOutput(boomer.NewPrometheusPusherOutput(prometheusPushgatewayURL, "hrp", hrpBoomer.GetMode())) + if boomArgs.PrometheusPushgatewayURL != "" { + hrpBoomer.AddOutput(boomer.NewPrometheusPusherOutput(boomArgs.PrometheusPushgatewayURL, "hrp", hrpBoomer.GetMode())) } - hrpBoomer.SetDisableKeepAlive(disableKeepalive) - hrpBoomer.SetDisableCompression(disableCompression) + hrpBoomer.SetDisableKeepAlive(boomArgs.DisableKeepalive) + hrpBoomer.SetDisableCompression(boomArgs.DisableCompression) hrpBoomer.SetClientTransport() - hrpBoomer.EnableCPUProfile(cpuProfile, cpuProfileDuration) - hrpBoomer.EnableMemoryProfile(memoryProfile, memoryProfileDuration) + hrpBoomer.EnableCPUProfile(boomArgs.CPUProfile, boomArgs.CPUProfileDuration) + hrpBoomer.EnableMemoryProfile(boomArgs.MemoryProfile, boomArgs.MemoryProfileDuration) hrpBoomer.EnableGracefulQuit() hrpBoomer.Run(paths...) }, } -var ( - spawnCount int - spawnRate float64 - maxRPS int64 - loopCount int64 - requestIncreaseRate string - memoryProfile string - memoryProfileDuration time.Duration - cpuProfile string - cpuProfileDuration time.Duration - prometheusPushgatewayURL string - disableConsoleOutput bool - disableCompression bool - disableKeepalive bool -) +type BoomArgs struct { + SpawnCount int `json:"spawn-count,omitempty" yaml:"spawn-count,omitempty"` + SpawnRate float64 `json:"spawn-rate,omitempty" yaml:"spawn-rate,omitempty"` + MaxRPS int64 `json:"max-rps,omitempty" yaml:"max-rps,omitempty"` + LoopCount int64 `json:"loop-count,omitempty" yaml:"loop-count,omitempty"` + RequestIncreaseRate string `json:"request-increase-rate,omitempty" yaml:"request-increase-rate,omitempty"` + MemoryProfile string `json:"memory-profile,omitempty" yaml:"memory-profile,omitempty"` + MemoryProfileDuration time.Duration `json:"memory-profile-duration" yaml:"memory-profile-duration"` + CPUProfile string `json:"cpu-profile,omitempty" yaml:"cpu-profile,omitempty"` + CPUProfileDuration time.Duration `json:"cpu-profile-duration,omitempty" yaml:"cpu-profile-duration,omitempty"` + PrometheusPushgatewayURL string `json:"prometheus-gateway,omitempty" yaml:"prometheus-gateway,omitempty"` + DisableConsoleOutput bool `json:"disable-console-output,omitempty" yaml:"disable-console-output,omitempty"` + DisableCompression bool `json:"disable-compression,omitempty" yaml:"disable-compression,omitempty"` + DisableKeepalive bool `json:"disable-keepalive,omitempty" yaml:"disable-keepalive,omitempty"` + profile string +} + +var boomArgs BoomArgs func init() { rootCmd.AddCommand(boomCmd) - boomCmd.Flags().Int64Var(&maxRPS, "max-rps", 0, "Max RPS that boomer can generate, disabled by default.") - boomCmd.Flags().StringVar(&requestIncreaseRate, "request-increase-rate", "-1", "Request increase rate, disabled by default.") - boomCmd.Flags().IntVar(&spawnCount, "spawn-count", 1, "The number of users to spawn for load testing") - boomCmd.Flags().Float64Var(&spawnRate, "spawn-rate", 1, "The rate for spawning users") - boomCmd.Flags().Int64Var(&loopCount, "loop-count", -1, "The specify running cycles for load testing") - boomCmd.Flags().StringVar(&memoryProfile, "mem-profile", "", "Enable memory profiling.") - boomCmd.Flags().DurationVar(&memoryProfileDuration, "mem-profile-duration", 30*time.Second, "Memory profile duration.") - boomCmd.Flags().StringVar(&cpuProfile, "cpu-profile", "", "Enable CPU profiling.") - boomCmd.Flags().DurationVar(&cpuProfileDuration, "cpu-profile-duration", 30*time.Second, "CPU profile duration.") - boomCmd.Flags().StringVar(&prometheusPushgatewayURL, "prometheus-gateway", "", "Prometheus Pushgateway url.") - boomCmd.Flags().BoolVar(&disableConsoleOutput, "disable-console-output", false, "Disable console output.") - boomCmd.Flags().BoolVar(&disableCompression, "disable-compression", false, "Disable compression") - boomCmd.Flags().BoolVar(&disableKeepalive, "disable-keepalive", false, "Disable keepalive") + boomCmd.Flags().Int64Var(&boomArgs.MaxRPS, "max-rps", 0, "Max RPS that boomer can generate, disabled by default.") + boomCmd.Flags().StringVar(&boomArgs.RequestIncreaseRate, "request-increase-rate", "-1", "Request increase rate, disabled by default.") + boomCmd.Flags().IntVar(&boomArgs.SpawnCount, "spawn-count", 1, "The number of users to spawn for load testing") + boomCmd.Flags().Float64Var(&boomArgs.SpawnRate, "spawn-rate", 1, "The rate for spawning users") + boomCmd.Flags().Int64Var(&boomArgs.LoopCount, "loop-count", -1, "The specify running cycles for load testing") + boomCmd.Flags().StringVar(&boomArgs.MemoryProfile, "mem-profile", "", "Enable memory profiling.") + boomCmd.Flags().DurationVar(&boomArgs.MemoryProfileDuration, "mem-profile-duration", 30*time.Second, "Memory profile duration.") + boomCmd.Flags().StringVar(&boomArgs.CPUProfile, "cpu-profile", "", "Enable CPU profiling.") + boomCmd.Flags().DurationVar(&boomArgs.CPUProfileDuration, "cpu-profile-duration", 30*time.Second, "CPU profile duration.") + boomCmd.Flags().StringVar(&boomArgs.PrometheusPushgatewayURL, "prometheus-gateway", "", "Prometheus Pushgateway url.") + boomCmd.Flags().BoolVar(&boomArgs.DisableConsoleOutput, "disable-console-output", false, "Disable console output.") + boomCmd.Flags().BoolVar(&boomArgs.DisableCompression, "disable-compression", false, "Disable compression") + boomCmd.Flags().BoolVar(&boomArgs.DisableKeepalive, "disable-keepalive", false, "Disable keepalive") + boomCmd.Flags().StringVar(&boomArgs.profile, "profile", "", "profile for load testing") } diff --git a/hrp/internal/scaffold/main.go b/hrp/internal/scaffold/main.go index ca505d68..ba6a1c24 100644 --- a/hrp/internal/scaffold/main.go +++ b/hrp/internal/scaffold/main.go @@ -6,6 +6,7 @@ import ( "os" "os/exec" "path/filepath" + "time" "github.com/httprunner/funplugin/shared" "github.com/pkg/errors" @@ -13,6 +14,7 @@ import ( "github.com/httprunner/httprunner/v4/hrp/internal/builtin" "github.com/httprunner/httprunner/v4/hrp/internal/sdk" + "github.com/httprunner/httprunner/v4/hrp/internal/version" ) type PluginType string @@ -23,6 +25,12 @@ const ( Go PluginType = "go" ) +type ProjectInfo struct { + ProjectName string `json:"project_name,omitempty" yaml:"project_name,omitempty"` + CreateTime time.Time `json:"create_time,omitempty" yaml:"create_time,omitempty"` + Version string `json:"hrp_version,omitempty" yaml:"hrp_version,omitempty"` +} + //go:embed templates/* var templatesDir embed.FS @@ -88,8 +96,17 @@ func CreateScaffold(projectName string, pluginType PluginType, force bool) error return err } + projectInfo := &ProjectInfo{ + ProjectName: projectName, + CreateTime: time.Now(), + Version: version.VERSION, + } + + // dump project information to file + err := builtin.Dump2JSON(projectInfo, filepath.Join(projectName, "proj.json")) + // create .gitignore - err := CopyFile("templates/gitignore", filepath.Join(projectName, ".gitignore")) + err = CopyFile("templates/gitignore", filepath.Join(projectName, ".gitignore")) if err != nil { return err } diff --git a/hrp/parameters.go b/hrp/parameters.go index 06007025..376232fa 100644 --- a/hrp/parameters.go +++ b/hrp/parameters.go @@ -20,9 +20,9 @@ type TParamsConfig struct { type iteratorPickOrder string const ( - strategySequential iteratorPickOrder = "sequential" - strategyRandom iteratorPickOrder = "random" - strategyUnique iteratorPickOrder = "unique" + pickOrderSequential iteratorPickOrder = "sequential" + pickOrderRandom iteratorPickOrder = "random" + pickOrderUnique iteratorPickOrder = "unique" ) /* @@ -67,15 +67,15 @@ func newParametersIterator(parameters map[string]Parameters, config *TParamsConf parametersList := make([]Parameters, 0) for paramName := range parameters { - // check parameter individual strategy + // check parameter individual pick order strategy strategy, ok := config.Strategies[paramName] if !ok { - // default to overall pick order + // default to overall pick order strategy strategy.PickOrder = config.PickOrder } - // group parameters by strategy - if strategy.PickOrder == strategyRandom { + // group parameters by pick order strategy + if strategy.PickOrder == pickOrderRandom { iterator.randomParameterNames = append(iterator.randomParameterNames, paramName) } else { parametersList = append(parametersList, parameters[paramName]) diff --git a/hrp/plugin.go b/hrp/plugin.go index 7929d030..ab2f34e6 100644 --- a/hrp/plugin.go +++ b/hrp/plugin.go @@ -118,7 +118,7 @@ func locateFile(startPath string, destFile string) (string, error) { return locateFile(parentDir, destFile) } -func getProjectRootDirPath(path string) (rootDir string, err error) { +func GetProjectRootDirPath(path string) (rootDir string, err error) { pluginPath, err := locatePlugin(path) if err == nil { rootDir = filepath.Dir(pluginPath) diff --git a/hrp/testcase.go b/hrp/testcase.go index 05c2b051..6cefbbb7 100644 --- a/hrp/testcase.go +++ b/hrp/testcase.go @@ -75,7 +75,7 @@ func (path *TestCasePath) ToTestCase() (*TestCase, error) { } // locate project root dir by plugin path - projectRootDir, err := getProjectRootDirPath(casePath) + projectRootDir, err := GetProjectRootDirPath(casePath) if err != nil { return nil, errors.Wrap(err, "failed to get project root dir") } From f155d6007dd503be92d41c5698237593977505f6 Mon Sep 17 00:00:00 2001 From: xucong053 <xucong053@outlook.com> Date: Fri, 20 May 2022 15:03:37 +0800 Subject: [PATCH 047/109] fix: unittest --- docs/cmd/hrp.md | 2 +- docs/cmd/hrp_boom.md | 3 ++- docs/cmd/hrp_convert.md | 2 +- docs/cmd/hrp_har2case.md | 2 +- docs/cmd/hrp_pytest.md | 2 +- docs/cmd/hrp_run.md | 2 +- docs/cmd/hrp_startproject.md | 2 +- hrp/internal/scaffold/main.go | 13 ++++++++++++- hrp/runner_test.go | 2 +- hrp/testcase.go | 4 ++-- 10 files changed, 23 insertions(+), 11 deletions(-) diff --git a/docs/cmd/hrp.md b/docs/cmd/hrp.md index 2620d07f..097cf3fb 100644 --- a/docs/cmd/hrp.md +++ b/docs/cmd/hrp.md @@ -36,4 +36,4 @@ Copyright 2017 debugtalk * [hrp run](hrp_run.md) - run API test with go engine * [hrp startproject](hrp_startproject.md) - create a scaffold project -###### Auto generated by spf13/cobra on 9-May-2022 +###### Auto generated by spf13/cobra on 20-May-2022 diff --git a/docs/cmd/hrp_boom.md b/docs/cmd/hrp_boom.md index ad27f7b2..7277436c 100644 --- a/docs/cmd/hrp_boom.md +++ b/docs/cmd/hrp_boom.md @@ -31,6 +31,7 @@ hrp boom [flags] --max-rps int Max RPS that boomer can generate, disabled by default. --mem-profile string Enable memory profiling. --mem-profile-duration duration Memory profile duration. (default 30s) + --profile string profile for load testing --prometheus-gateway string Prometheus Pushgateway url. --request-increase-rate string Request increase rate, disabled by default. (default "-1") --spawn-count int The number of users to spawn for load testing (default 1) @@ -41,4 +42,4 @@ hrp boom [flags] * [hrp](hrp.md) - Next-Generation API Testing Solution. -###### Auto generated by spf13/cobra on 9-May-2022 +###### Auto generated by spf13/cobra on 20-May-2022 diff --git a/docs/cmd/hrp_convert.md b/docs/cmd/hrp_convert.md index 7390e9cc..3b55f035 100644 --- a/docs/cmd/hrp_convert.md +++ b/docs/cmd/hrp_convert.md @@ -18,4 +18,4 @@ hrp convert $path... [flags] * [hrp](hrp.md) - Next-Generation API Testing Solution. -###### Auto generated by spf13/cobra on 9-May-2022 +###### Auto generated by spf13/cobra on 20-May-2022 diff --git a/docs/cmd/hrp_har2case.md b/docs/cmd/hrp_har2case.md index db6b8b10..41e46787 100644 --- a/docs/cmd/hrp_har2case.md +++ b/docs/cmd/hrp_har2case.md @@ -24,4 +24,4 @@ hrp har2case $har_path... [flags] * [hrp](hrp.md) - Next-Generation API Testing Solution. -###### Auto generated by spf13/cobra on 9-May-2022 +###### Auto generated by spf13/cobra on 20-May-2022 diff --git a/docs/cmd/hrp_pytest.md b/docs/cmd/hrp_pytest.md index b2217ca1..0bdbab3c 100644 --- a/docs/cmd/hrp_pytest.md +++ b/docs/cmd/hrp_pytest.md @@ -16,4 +16,4 @@ hrp pytest $path ... [flags] * [hrp](hrp.md) - Next-Generation API Testing Solution. -###### Auto generated by spf13/cobra on 9-May-2022 +###### Auto generated by spf13/cobra on 20-May-2022 diff --git a/docs/cmd/hrp_run.md b/docs/cmd/hrp_run.md index 6ffdd6d2..8a1fc59b 100644 --- a/docs/cmd/hrp_run.md +++ b/docs/cmd/hrp_run.md @@ -35,4 +35,4 @@ hrp run $path... [flags] * [hrp](hrp.md) - Next-Generation API Testing Solution. -###### Auto generated by spf13/cobra on 9-May-2022 +###### Auto generated by spf13/cobra on 20-May-2022 diff --git a/docs/cmd/hrp_startproject.md b/docs/cmd/hrp_startproject.md index 4987cd6d..b669b71d 100644 --- a/docs/cmd/hrp_startproject.md +++ b/docs/cmd/hrp_startproject.md @@ -20,4 +20,4 @@ hrp startproject $project_name [flags] * [hrp](hrp.md) - Next-Generation API Testing Solution. -###### Auto generated by spf13/cobra on 9-May-2022 +###### Auto generated by spf13/cobra on 20-May-2022 diff --git a/hrp/internal/scaffold/main.go b/hrp/internal/scaffold/main.go index ba6a1c24..a0fe5efa 100644 --- a/hrp/internal/scaffold/main.go +++ b/hrp/internal/scaffold/main.go @@ -27,6 +27,7 @@ const ( type ProjectInfo struct { ProjectName string `json:"project_name,omitempty" yaml:"project_name,omitempty"` + ProjectPath string `json:"project_path,omitempty" yaml:"project_path,omitempty"` CreateTime time.Time `json:"create_time,omitempty" yaml:"create_time,omitempty"` Version string `json:"hrp_version,omitempty" yaml:"hrp_version,omitempty"` } @@ -76,6 +77,12 @@ func CreateScaffold(projectName string, pluginType PluginType, force bool) error os.RemoveAll(projectName) } + // get project abs path + projectPath, err := filepath.Abs(projectName) + if err != nil { + projectPath = projectName + } + // create project folders if err := builtin.CreateFolder(projectName); err != nil { return err @@ -98,12 +105,16 @@ func CreateScaffold(projectName string, pluginType PluginType, force bool) error projectInfo := &ProjectInfo{ ProjectName: projectName, + ProjectPath: projectPath, CreateTime: time.Now(), Version: version.VERSION, } // dump project information to file - err := builtin.Dump2JSON(projectInfo, filepath.Join(projectName, "proj.json")) + err = builtin.Dump2JSON(projectInfo, filepath.Join(projectName, "proj.json")) + if err != nil { + return err + } // create .gitignore err = CopyFile("templates/gitignore", filepath.Join(projectName, ".gitignore")) diff --git a/hrp/runner_test.go b/hrp/runner_test.go index 2d743e6c..d03f8d75 100644 --- a/hrp/runner_test.go +++ b/hrp/runner_test.go @@ -207,7 +207,7 @@ func TestLoadTestCases(t *testing.T) { if !assert.Nil(t, err) { t.Fatal() } - if !assert.Equal(t, len(testCases), 4) { + if !assert.Equal(t, 4, len(testCases)) { t.Fatal() } diff --git a/hrp/testcase.go b/hrp/testcase.go index 6cefbbb7..2a4e589c 100644 --- a/hrp/testcase.go +++ b/hrp/testcase.go @@ -287,8 +287,8 @@ func LoadTestCases(iTestCases ...ITestCase) ([]*TestCase, error) { testCasePath := TestCasePath(path) tc, err := testCasePath.ToTestCase() if err != nil { - log.Error().Err(err).Str("path", path).Msg("load testcase failed") - return errors.Wrap(err, "load testcase failed") + log.Warn().Err(err).Str("path", path).Msg("load testcase failed") + return nil } testCases = append(testCases, tc) return nil From 2247c9df8286c868490cb87fcab337a68b74dd4a Mon Sep 17 00:00:00 2001 From: debugtalk <mail@debugtalk.com> Date: Sat, 21 May 2022 22:16:39 +0800 Subject: [PATCH 048/109] update changelog --- docs/CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index e93ec00d..823a3330 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -1,12 +1,13 @@ # Release History -## v4.1.0-beta (2022-05-19) +## v4.1.0-beta (2022-05-21) - feat: add pre-commit-hook to format go/python code **go version** - feat: add boomer mode(standalone/master/worker) +- feat: support load testing with specified `--profile` configuration file - fix: step request elapsed timing should contain ContentTransfer part - fix #1288: unable to go get httprunner v4 - fix: panic when config didn't exist in testcase file From 880c4b3af70fd926952ed8ce6719d6807c1b2648 Mon Sep 17 00:00:00 2001 From: debugtalk <mail@debugtalk.com> Date: Sun, 22 May 2022 15:23:53 +0800 Subject: [PATCH 049/109] feat: add wiki sub-command to open httprunner website --- docs/CHANGELOG.md | 4 ++++ hrp/cmd/scaffold.go | 7 ++++--- hrp/cmd/wiki.go | 23 +++++++++++++++++++++++ hrp/internal/wiki/main.go | 12 ++++++++++++ hrp/internal/wiki/open_darwin.go | 3 +++ hrp/internal/wiki/open_linux.go | 3 +++ hrp/internal/wiki/open_windows.go | 3 +++ 7 files changed, 52 insertions(+), 3 deletions(-) create mode 100644 hrp/cmd/wiki.go create mode 100644 hrp/internal/wiki/main.go create mode 100644 hrp/internal/wiki/open_darwin.go create mode 100644 hrp/internal/wiki/open_linux.go create mode 100644 hrp/internal/wiki/open_windows.go diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 823a3330..1f9870aa 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -1,5 +1,9 @@ # Release History +## v4.1.0 (2022-05-22) + +- feat: add `wiki` sub-command to open httprunner website + ## v4.1.0-beta (2022-05-21) - feat: add pre-commit-hook to format go/python code diff --git a/hrp/cmd/scaffold.go b/hrp/cmd/scaffold.go index f3441b82..93a8e4b8 100644 --- a/hrp/cmd/scaffold.go +++ b/hrp/cmd/scaffold.go @@ -11,9 +11,10 @@ import ( ) var scaffoldCmd = &cobra.Command{ - Use: "startproject $project_name", - Short: "create a scaffold project", - Args: cobra.ExactValidArgs(1), + Use: "startproject $project_name", + Aliases: []string{"scaffold"}, + Short: "create a scaffold project", + Args: cobra.ExactValidArgs(1), PreRun: func(cmd *cobra.Command, args []string) { setLogLevel(logLevel) }, diff --git a/hrp/cmd/wiki.go b/hrp/cmd/wiki.go new file mode 100644 index 00000000..7774a740 --- /dev/null +++ b/hrp/cmd/wiki.go @@ -0,0 +1,23 @@ +package cmd + +import ( + "github.com/spf13/cobra" + + "github.com/httprunner/httprunner/v4/hrp/internal/wiki" +) + +var wikiCmd = &cobra.Command{ + Use: "wiki", + Aliases: []string{"info", "docs", "doc"}, + Short: "visit https://httprunner.com", + PreRun: func(cmd *cobra.Command, args []string) { + setLogLevel(logLevel) + }, + RunE: func(cmd *cobra.Command, args []string) error { + return wiki.OpenWiki() + }, +} + +func init() { + rootCmd.AddCommand(wikiCmd) +} diff --git a/hrp/internal/wiki/main.go b/hrp/internal/wiki/main.go new file mode 100644 index 00000000..0c4cdb44 --- /dev/null +++ b/hrp/internal/wiki/main.go @@ -0,0 +1,12 @@ +package wiki + +import ( + "os/exec" + + "github.com/rs/zerolog/log" +) + +func OpenWiki() error { + log.Info().Msgf("%s https://httprunner.com", openCmd) + return exec.Command(openCmd, "https://httprunner.com").Run() +} diff --git a/hrp/internal/wiki/open_darwin.go b/hrp/internal/wiki/open_darwin.go new file mode 100644 index 00000000..a8856c1f --- /dev/null +++ b/hrp/internal/wiki/open_darwin.go @@ -0,0 +1,3 @@ +package wiki + +const openCmd = "open" diff --git a/hrp/internal/wiki/open_linux.go b/hrp/internal/wiki/open_linux.go new file mode 100644 index 00000000..d20152c4 --- /dev/null +++ b/hrp/internal/wiki/open_linux.go @@ -0,0 +1,3 @@ +package wiki + +const openCmd = "xdg-open" diff --git a/hrp/internal/wiki/open_windows.go b/hrp/internal/wiki/open_windows.go new file mode 100644 index 00000000..981253da --- /dev/null +++ b/hrp/internal/wiki/open_windows.go @@ -0,0 +1,3 @@ +package wiki + +const openCmd = "explorer" From 7c85630c3ddbbadf401c9f85e1dd4e8cbb3be2ef Mon Sep 17 00:00:00 2001 From: debugtalk <mail@debugtalk.com> Date: Mon, 23 May 2022 14:43:14 +0800 Subject: [PATCH 050/109] fix #1309: locate plugin file upward recursively until system root dir --- docs/CHANGELOG.md | 6 +++++- hrp/plugin.go | 10 ++-------- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 1f9870aa..83e6aa11 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -1,9 +1,13 @@ # Release History -## v4.1.0 (2022-05-22) +## v4.1.0 (2022-05-23) - feat: add `wiki` sub-command to open httprunner website +**go version** + +- fix #1309: locate plugin file upward recursively until system root dir + ## v4.1.0-beta (2022-05-21) - feat: add pre-commit-hook to format go/python code diff --git a/hrp/plugin.go b/hrp/plugin.go index ab2f34e6..67a86cfe 100644 --- a/hrp/plugin.go +++ b/hrp/plugin.go @@ -78,11 +78,11 @@ func locatePlugin(path string) (pluginPath string, err error) { return } + log.Warn().Err(err).Str("path", path).Msg("plugin file not found") return "", fmt.Errorf("plugin file not found") } -// locateFile searches destFile upward recursively until current -// working directory or system root dir. +// locateFile searches destFile upward recursively until system root dir func locateFile(startPath string, destFile string) (string, error) { stat, err := os.Stat(startPath) if os.IsNotExist(err) { @@ -103,12 +103,6 @@ func locateFile(startPath string, destFile string) (string, error) { return pluginPath, nil } - // current working directory - cwd, _ := os.Getwd() - if startDir == cwd { - return "", fmt.Errorf("searched to CWD, plugin file not found") - } - // system root dir parentDir, _ := filepath.Abs(filepath.Dir(startDir)) if parentDir == startDir { From f317fa05c00002e9867a4bdbe45314afcee39ab7 Mon Sep 17 00:00:00 2001 From: debugtalk <mail@debugtalk.com> Date: Mon, 23 May 2022 21:16:23 +0800 Subject: [PATCH 051/109] fix #1308: load .env file as environment variables --- docs/CHANGELOG.md | 1 + examples/demo-with-go-plugin/proj.json | 6 ++++ .../testcases/requests.json | 4 +-- .../testcases/requests.yml | 4 +-- examples/demo-with-py-plugin/proj.json | 6 ++++ .../testcases/requests.json | 4 +-- .../testcases/requests.yml | 4 +-- examples/demo-without-plugin/proj.json | 6 ++++ hrp/config.go | 2 ++ hrp/internal/builtin/utils.go | 33 +++++++++++++++++++ hrp/internal/scaffold/main.go | 2 +- .../templates/testcases/demo_requests.json | 4 +-- .../templates/testcases/demo_requests.yml | 4 +-- hrp/testcase.go | 10 ++++++ 14 files changed, 77 insertions(+), 13 deletions(-) create mode 100644 examples/demo-with-go-plugin/proj.json create mode 100644 examples/demo-with-py-plugin/proj.json create mode 100644 examples/demo-without-plugin/proj.json diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 83e6aa11..a9a7b702 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -6,6 +6,7 @@ **go version** +- fix #1308: load `.env` file as environment variables - fix #1309: locate plugin file upward recursively until system root dir ## v4.1.0-beta (2022-05-21) diff --git a/examples/demo-with-go-plugin/proj.json b/examples/demo-with-go-plugin/proj.json new file mode 100644 index 00000000..b8ac381a --- /dev/null +++ b/examples/demo-with-go-plugin/proj.json @@ -0,0 +1,6 @@ +{ + "project_name": "demo-with-go-plugin", + "project_path": "/Users/debugtalk/MyProjects/HttpRunner-dev/httprunner/examples/demo-with-go-plugin", + "create_time": "2022-05-24T11:10:10.127839+08:00", + "hrp_version": "v4.1.0-beta" +} \ No newline at end of file diff --git a/examples/demo-with-go-plugin/testcases/requests.json b/examples/demo-with-go-plugin/testcases/requests.json index b13f3837..9b4214d7 100644 --- a/examples/demo-with-go-plugin/testcases/requests.json +++ b/examples/demo-with-go-plugin/testcases/requests.json @@ -17,7 +17,7 @@ { "name": "get with params", "variables": { - "foo1": "bar11", + "foo1": "${ENV(USERNAME)}", "foo2": "bar21", "sum_v": "${sum_two_int(1, 2)}" }, @@ -46,7 +46,7 @@ { "eq": [ "body.args.foo1", - "bar11" + "debugtalk" ] }, { diff --git a/examples/demo-with-go-plugin/testcases/requests.yml b/examples/demo-with-go-plugin/testcases/requests.yml index 86d1b9cc..5ed53dd8 100644 --- a/examples/demo-with-go-plugin/testcases/requests.yml +++ b/examples/demo-with-go-plugin/testcases/requests.yml @@ -13,7 +13,7 @@ teststeps: - name: get with params variables: - foo1: bar11 + foo1: ${ENV(USERNAME)} foo2: bar21 sum_v: "${sum_two_int(1, 2)}" request: @@ -29,7 +29,7 @@ teststeps: foo3: "body.args.foo2" validate: - eq: ["status_code", 200] - - eq: ["body.args.foo1", "bar11"] + - eq: ["body.args.foo1", "debugtalk"] - eq: ["body.args.sum_v", "3"] - eq: ["body.args.foo2", "bar21"] - diff --git a/examples/demo-with-py-plugin/proj.json b/examples/demo-with-py-plugin/proj.json new file mode 100644 index 00000000..fd0b8628 --- /dev/null +++ b/examples/demo-with-py-plugin/proj.json @@ -0,0 +1,6 @@ +{ + "project_name": "demo-with-py-plugin", + "project_path": "/Users/debugtalk/MyProjects/HttpRunner-dev/httprunner/examples/demo-with-py-plugin", + "create_time": "2022-05-24T11:10:15.544763+08:00", + "hrp_version": "v4.1.0-beta" +} \ No newline at end of file diff --git a/examples/demo-with-py-plugin/testcases/requests.json b/examples/demo-with-py-plugin/testcases/requests.json index b13f3837..9b4214d7 100644 --- a/examples/demo-with-py-plugin/testcases/requests.json +++ b/examples/demo-with-py-plugin/testcases/requests.json @@ -17,7 +17,7 @@ { "name": "get with params", "variables": { - "foo1": "bar11", + "foo1": "${ENV(USERNAME)}", "foo2": "bar21", "sum_v": "${sum_two_int(1, 2)}" }, @@ -46,7 +46,7 @@ { "eq": [ "body.args.foo1", - "bar11" + "debugtalk" ] }, { diff --git a/examples/demo-with-py-plugin/testcases/requests.yml b/examples/demo-with-py-plugin/testcases/requests.yml index 86d1b9cc..5ed53dd8 100644 --- a/examples/demo-with-py-plugin/testcases/requests.yml +++ b/examples/demo-with-py-plugin/testcases/requests.yml @@ -13,7 +13,7 @@ teststeps: - name: get with params variables: - foo1: bar11 + foo1: ${ENV(USERNAME)} foo2: bar21 sum_v: "${sum_two_int(1, 2)}" request: @@ -29,7 +29,7 @@ teststeps: foo3: "body.args.foo2" validate: - eq: ["status_code", 200] - - eq: ["body.args.foo1", "bar11"] + - eq: ["body.args.foo1", "debugtalk"] - eq: ["body.args.sum_v", "3"] - eq: ["body.args.foo2", "bar21"] - diff --git a/examples/demo-without-plugin/proj.json b/examples/demo-without-plugin/proj.json new file mode 100644 index 00000000..c6eef7d9 --- /dev/null +++ b/examples/demo-without-plugin/proj.json @@ -0,0 +1,6 @@ +{ + "project_name": "demo-without-plugin", + "project_path": "/Users/debugtalk/MyProjects/HttpRunner-dev/httprunner/examples/demo-without-plugin", + "create_time": "2022-05-24T11:10:16.993462+08:00", + "hrp_version": "v4.1.0-beta" +} \ No newline at end of file diff --git a/hrp/config.go b/hrp/config.go index af903bc5..76912aa7 100644 --- a/hrp/config.go +++ b/hrp/config.go @@ -10,6 +10,7 @@ import ( func NewConfig(name string) *TConfig { return &TConfig{ Name: name, + Env: make(map[string]string), Variables: make(map[string]interface{}), } } @@ -21,6 +22,7 @@ type TConfig struct { Verify bool `json:"verify,omitempty" yaml:"verify,omitempty"` BaseURL string `json:"base_url,omitempty" yaml:"base_url,omitempty"` Headers map[string]string `json:"headers,omitempty" yaml:"headers,omitempty"` + Env map[string]string `json:"env,omitempty" yaml:"env,omitempty"` Variables map[string]interface{} `json:"variables,omitempty" yaml:"variables,omitempty"` Parameters map[string]interface{} `json:"parameters,omitempty" yaml:"parameters,omitempty"` ParametersSetting *TParamsConfig `json:"parameters_setting,omitempty" yaml:"parameters_setting,omitempty"` diff --git a/hrp/internal/builtin/utils.go b/hrp/internal/builtin/utils.go index cacad024..8bdc661d 100644 --- a/hrp/internal/builtin/utils.go +++ b/hrp/internal/builtin/utils.go @@ -294,12 +294,45 @@ func LoadFile(path string, structObj interface{}) (err error) { err = decoder.Decode(structObj) case ".yaml", ".yml": err = yaml.Unmarshal(file, structObj) + case ".env": + err = parseEnvContent(file, structObj) default: err = ErrUnsupportedFileExt } return err } +func parseEnvContent(file []byte, obj interface{}) error { + envMap := obj.(map[string]string) + lines := strings.Split(string(file), "\n") + for _, line := range lines { + line = strings.TrimSpace(line) + if line == "" || strings.HasPrefix(line, "#") { + // empty line or comment line + continue + } + var kv []string + if strings.Contains(line, "=") { + kv = strings.SplitN(line, "=", 2) + } + if strings.Contains(line, ":") { + kv = strings.SplitN(line, ":", 2) + } + if len(kv) != 2 { + return errors.New(".env format error") + } + + key := strings.TrimSpace(kv[0]) + value := strings.TrimSpace(kv[1]) + envMap[key] = value + + // set env + log.Info().Str("key", key).Msg("set env") + os.Setenv(key, value) + } + return nil +} + func loadFromCSV(path string) []map[string]interface{} { log.Info().Str("path", path).Msg("load csv file") file, err := readFile(path) diff --git a/hrp/internal/scaffold/main.go b/hrp/internal/scaffold/main.go index a0fe5efa..3392d960 100644 --- a/hrp/internal/scaffold/main.go +++ b/hrp/internal/scaffold/main.go @@ -104,7 +104,7 @@ func CreateScaffold(projectName string, pluginType PluginType, force bool) error } projectInfo := &ProjectInfo{ - ProjectName: projectName, + ProjectName: filepath.Base(projectName), ProjectPath: projectPath, CreateTime: time.Now(), Version: version.VERSION, diff --git a/hrp/internal/scaffold/templates/testcases/demo_requests.json b/hrp/internal/scaffold/templates/testcases/demo_requests.json index b13f3837..9b4214d7 100644 --- a/hrp/internal/scaffold/templates/testcases/demo_requests.json +++ b/hrp/internal/scaffold/templates/testcases/demo_requests.json @@ -17,7 +17,7 @@ { "name": "get with params", "variables": { - "foo1": "bar11", + "foo1": "${ENV(USERNAME)}", "foo2": "bar21", "sum_v": "${sum_two_int(1, 2)}" }, @@ -46,7 +46,7 @@ { "eq": [ "body.args.foo1", - "bar11" + "debugtalk" ] }, { diff --git a/hrp/internal/scaffold/templates/testcases/demo_requests.yml b/hrp/internal/scaffold/templates/testcases/demo_requests.yml index 86d1b9cc..5ed53dd8 100644 --- a/hrp/internal/scaffold/templates/testcases/demo_requests.yml +++ b/hrp/internal/scaffold/templates/testcases/demo_requests.yml @@ -13,7 +13,7 @@ teststeps: - name: get with params variables: - foo1: bar11 + foo1: ${ENV(USERNAME)} foo2: bar21 sum_v: "${sum_two_int(1, 2)}" request: @@ -29,7 +29,7 @@ teststeps: foo3: "body.args.foo2" validate: - eq: ["status_code", 200] - - eq: ["body.args.foo1", "bar11"] + - eq: ["body.args.foo1", "debugtalk"] - eq: ["body.args.sum_v", "3"] - eq: ["body.args.foo2", "bar21"] - diff --git a/hrp/testcase.go b/hrp/testcase.go index 2a4e589c..0aa548ec 100644 --- a/hrp/testcase.go +++ b/hrp/testcase.go @@ -80,6 +80,16 @@ func (path *TestCasePath) ToTestCase() (*TestCase, error) { return nil, errors.Wrap(err, "failed to get project root dir") } + // load .env file + dotEnvPath := filepath.Join(projectRootDir, ".env") + if builtin.IsFilePathExists(dotEnvPath) { + testCase.Config.Env = make(map[string]string) + err = builtin.LoadFile(dotEnvPath, testCase.Config.Env) + if err != nil { + return nil, errors.Wrap(err, "failed to load .env file") + } + } + for _, step := range tc.TestSteps { if step.API != nil { apiPath, ok := step.API.(string) From d0b2568797a9bd8bb5ca216360bc92641737f8df Mon Sep 17 00:00:00 2001 From: buyuxiang <347586493@qq.com> Date: Tue, 24 May 2022 13:36:34 +0800 Subject: [PATCH 052/109] refactor: hrp convert --- docs/CHANGELOG.md | 1 + docs/cmd/hrp.md | 4 +- docs/cmd/hrp_boom.md | 2 +- docs/cmd/hrp_convert.md | 14 +- docs/cmd/hrp_har2case.md | 2 +- docs/cmd/hrp_postman2case.md | 26 - docs/cmd/hrp_pytest.md | 2 +- docs/cmd/hrp_run.md | 2 +- docs/cmd/hrp_startproject.md | 2 +- .../har/{profile.yml => profile_override.yml} | 1 + examples/data/postman2case/patch.yml | 4 - examples/data/postman2case/profile.yml | 4 +- .../data/postman2case/profile_override.yml | 5 + go.mod | 1 + go.sum | 27 +- hrp/cmd/convert.go | 55 +- hrp/cmd/har2case.go | 7 - hrp/cmd/postman2case.go | 79 -- hrp/internal/builtin/utils.go | 9 +- hrp/internal/convert/README.md | 68 ++ hrp/internal/convert/asset/flowgram.svg | 1 + hrp/internal/convert/case2script/main.go | 120 --- hrp/internal/convert/converter.go | 374 +++++++++ hrp/internal/convert/converter_gotest.go | 60 ++ hrp/internal/convert/converter_har.go | 716 ++++++++++++++++++ hrp/internal/convert/converter_har_test.go | 373 +++++++++ hrp/internal/convert/converter_json.go | 111 +++ .../core.go => converter_postman.go} | 343 +++++---- ...core_test.go => converter_postman_test.go} | 46 +- hrp/internal/convert/converter_pytest.go | 19 + hrp/internal/convert/converter_yaml.go | 94 +++ hrp/internal/convert/har2case/core.go | 87 +-- hrp/internal/convert/har2case/core_test.go | 32 +- .../convert/postman2case/collection.go | 74 -- .../convert/{case2script => }/testcase.tmpl | 0 hrp/step_api.go | 2 +- hrp/testcase.go | 104 ++- 37 files changed, 2245 insertions(+), 626 deletions(-) delete mode 100644 docs/cmd/hrp_postman2case.md rename examples/data/har/{profile.yml => profile_override.yml} (86%) delete mode 100644 examples/data/postman2case/patch.yml create mode 100644 examples/data/postman2case/profile_override.yml delete mode 100644 hrp/cmd/postman2case.go create mode 100644 hrp/internal/convert/README.md create mode 100644 hrp/internal/convert/asset/flowgram.svg delete mode 100644 hrp/internal/convert/case2script/main.go create mode 100644 hrp/internal/convert/converter.go create mode 100644 hrp/internal/convert/converter_gotest.go create mode 100644 hrp/internal/convert/converter_har.go create mode 100644 hrp/internal/convert/converter_har_test.go create mode 100644 hrp/internal/convert/converter_json.go rename hrp/internal/convert/{postman2case/core.go => converter_postman.go} (52%) rename hrp/internal/convert/{postman2case/core_test.go => converter_postman_test.go} (73%) create mode 100644 hrp/internal/convert/converter_pytest.go create mode 100644 hrp/internal/convert/converter_yaml.go delete mode 100644 hrp/internal/convert/postman2case/collection.go rename hrp/internal/convert/{case2script => }/testcase.tmpl (100%) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 118ede44..4a06287a 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -9,6 +9,7 @@ - fix: step request elapsed timing should contain ContentTransfer part - fix #1288: unable to go get httprunner v4 - feat: support converting Postman collection to HttpRunner testcase +- refactor: improve the extensibility of `hrp convert` using interface `ICaseConverter` **python version** diff --git a/docs/cmd/hrp.md b/docs/cmd/hrp.md index 2620d07f..578091e9 100644 --- a/docs/cmd/hrp.md +++ b/docs/cmd/hrp.md @@ -30,10 +30,10 @@ Copyright 2017 debugtalk ### SEE ALSO * [hrp boom](hrp_boom.md) - run load test with boomer -* [hrp convert](hrp_convert.md) - convert JSON/YAML testcases to pytest/gotest scripts +* [hrp convert](hrp_convert.md) - convert external cases to JSON/YAML/gotest/pytest testcases * [hrp har2case](hrp_har2case.md) - convert HAR to json/yaml testcase files * [hrp pytest](hrp_pytest.md) - run API test with pytest * [hrp run](hrp_run.md) - run API test with go engine * [hrp startproject](hrp_startproject.md) - create a scaffold project -###### Auto generated by spf13/cobra on 9-May-2022 +###### Auto generated by spf13/cobra on 23-May-2022 diff --git a/docs/cmd/hrp_boom.md b/docs/cmd/hrp_boom.md index ad27f7b2..37675fcc 100644 --- a/docs/cmd/hrp_boom.md +++ b/docs/cmd/hrp_boom.md @@ -41,4 +41,4 @@ hrp boom [flags] * [hrp](hrp.md) - Next-Generation API Testing Solution. -###### Auto generated by spf13/cobra on 9-May-2022 +###### Auto generated by spf13/cobra on 23-May-2022 diff --git a/docs/cmd/hrp_convert.md b/docs/cmd/hrp_convert.md index 7390e9cc..d4771aad 100644 --- a/docs/cmd/hrp_convert.md +++ b/docs/cmd/hrp_convert.md @@ -1,6 +1,6 @@ ## hrp convert -convert JSON/YAML testcases to pytest/gotest scripts +convert external cases to JSON/YAML/gotest/pytest testcases ``` hrp convert $path... [flags] @@ -9,13 +9,17 @@ hrp convert $path... [flags] ### Options ``` - --gotest convert to gotest scripts (TODO) - -h, --help help for convert - --pytest convert to pytest scripts (default true) + -h, --help help for convert + -d, --output-dir string specify output directory, default to the same dir with har file + -p, --profile string specify profile path to override headers (except for auto-generated headers) and cookies + --to-gotest convert to gotest scripts (TODO) + --to-json convert to JSON scripts (default) + --to-pytest convert to pytest scripts + --to-yaml convert to YAML scripts ``` ### SEE ALSO * [hrp](hrp.md) - Next-Generation API Testing Solution. -###### Auto generated by spf13/cobra on 9-May-2022 +###### Auto generated by spf13/cobra on 23-May-2022 diff --git a/docs/cmd/hrp_har2case.md b/docs/cmd/hrp_har2case.md index db6b8b10..0ef151a3 100644 --- a/docs/cmd/hrp_har2case.md +++ b/docs/cmd/hrp_har2case.md @@ -24,4 +24,4 @@ hrp har2case $har_path... [flags] * [hrp](hrp.md) - Next-Generation API Testing Solution. -###### Auto generated by spf13/cobra on 9-May-2022 +###### Auto generated by spf13/cobra on 23-May-2022 diff --git a/docs/cmd/hrp_postman2case.md b/docs/cmd/hrp_postman2case.md deleted file mode 100644 index 23c196e7..00000000 --- a/docs/cmd/hrp_postman2case.md +++ /dev/null @@ -1,26 +0,0 @@ -## hrp postman2case - -convert postman collection to json/yaml testcase files - -### Synopsis - -convert postman collection to json/yaml testcase files - -``` -hrp postman2case $postman_path... [flags] -``` - -### Options - -``` - -h, --help help for postman2case - -d, --output-dir string specify output directory, default to the same dir with postman collection file - -j, --to-json convert to JSON format (default true) - -y, --to-yaml convert to YAML format -``` - -### SEE ALSO - -* [hrp](hrp.md) - Next-Generation API Testing Solution. - -###### Auto generated by spf13/cobra on 12-May-2022 diff --git a/docs/cmd/hrp_pytest.md b/docs/cmd/hrp_pytest.md index b2217ca1..2ed3b104 100644 --- a/docs/cmd/hrp_pytest.md +++ b/docs/cmd/hrp_pytest.md @@ -16,4 +16,4 @@ hrp pytest $path ... [flags] * [hrp](hrp.md) - Next-Generation API Testing Solution. -###### Auto generated by spf13/cobra on 9-May-2022 +###### Auto generated by spf13/cobra on 23-May-2022 diff --git a/docs/cmd/hrp_run.md b/docs/cmd/hrp_run.md index 6ffdd6d2..ff4ba4a7 100644 --- a/docs/cmd/hrp_run.md +++ b/docs/cmd/hrp_run.md @@ -35,4 +35,4 @@ hrp run $path... [flags] * [hrp](hrp.md) - Next-Generation API Testing Solution. -###### Auto generated by spf13/cobra on 9-May-2022 +###### Auto generated by spf13/cobra on 23-May-2022 diff --git a/docs/cmd/hrp_startproject.md b/docs/cmd/hrp_startproject.md index 4987cd6d..d598c7aa 100644 --- a/docs/cmd/hrp_startproject.md +++ b/docs/cmd/hrp_startproject.md @@ -20,4 +20,4 @@ hrp startproject $project_name [flags] * [hrp](hrp.md) - Next-Generation API Testing Solution. -###### Auto generated by spf13/cobra on 9-May-2022 +###### Auto generated by spf13/cobra on 23-May-2022 diff --git a/examples/data/har/profile.yml b/examples/data/har/profile_override.yml similarity index 86% rename from examples/data/har/profile.yml rename to examples/data/har/profile_override.yml index 69963ba2..35236a52 100644 --- a/examples/data/har/profile.yml +++ b/examples/data/har/profile_override.yml @@ -1,3 +1,4 @@ +override: true headers: Content-Type: "application/x-www-form-urlencoded" cookies: diff --git a/examples/data/postman2case/patch.yml b/examples/data/postman2case/patch.yml deleted file mode 100644 index c657b5ef..00000000 --- a/examples/data/postman2case/patch.yml +++ /dev/null @@ -1,4 +0,0 @@ -headers: - User-Agent: "this header will be created or updated" -cookies: - Cookie1: "this cookie will be created or updated" diff --git a/examples/data/postman2case/profile.yml b/examples/data/postman2case/profile.yml index 42e2e9f4..c657b5ef 100644 --- a/examples/data/postman2case/profile.yml +++ b/examples/data/postman2case/profile.yml @@ -1,4 +1,4 @@ headers: - Header1: "all original headers will be overridden" + User-Agent: "this header will be created or updated" cookies: - Cookie1: "all original cookies will be overridden" \ No newline at end of file + Cookie1: "this cookie will be created or updated" diff --git a/examples/data/postman2case/profile_override.yml b/examples/data/postman2case/profile_override.yml new file mode 100644 index 00000000..bc620e50 --- /dev/null +++ b/examples/data/postman2case/profile_override.yml @@ -0,0 +1,5 @@ +override: true +headers: + Header1: "all original headers will be overridden" +cookies: + Cookie1: "all original cookies will be overridden" \ No newline at end of file diff --git a/go.mod b/go.mod index 5dc2859b..ddf4db03 100644 --- a/go.mod +++ b/go.mod @@ -7,6 +7,7 @@ require ( github.com/denisbrodbeck/machineid v1.0.1 github.com/fatih/color v1.13.0 github.com/getsentry/sentry-go v0.13.0 + github.com/go-openapi/spec v0.20.6 github.com/google/uuid v1.3.0 github.com/gorilla/websocket v1.4.1 github.com/httprunner/funplugin v0.4.5 diff --git a/go.sum b/go.sum index 62502254..26432fc7 100644 --- a/go.sum +++ b/go.sum @@ -88,6 +88,7 @@ github.com/cpuguy83/go-md2man v1.0.10 h1:BSKMNlYxDvnunlTymqtgONjNnaRV1sTpcovwwjF github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= github.com/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -131,6 +132,16 @@ github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9 github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-martini/martini v0.0.0-20170121215854-22fa46961aab/go.mod h1:/P9AEU963A2AYjv4d1V5eVL1CQbEJq6aCNHDDjibzu8= +github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY= +github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonreference v0.20.0 h1:MYlu0sBgChmCfJxxUKZ8g1cPWFOB37YSZqewK7OKeyA= +github.com/go-openapi/jsonreference v0.20.0/go.mod h1:Ag74Ico3lPc+zR+qjn4XBUmXymS4zJbYVCZmcgkasdo= +github.com/go-openapi/spec v0.20.6 h1:ich1RQ3WDbfoeTqTAb+5EIxNmpKVJZWBNah9RAT0jIQ= +github.com/go-openapi/spec v0.20.6/go.mod h1:2OpW+JddWPrpXSCIX8eOx7lZ5iyuWj3RYR6VaaBKcWA= +github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-openapi/swag v0.19.15 h1:D2NRCBzS9/pEY3gP9Nl8aDqGUcPFrwG2p+CNFrLyrCM= +github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= @@ -262,6 +273,8 @@ github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9Y github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= @@ -288,16 +301,20 @@ github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxv github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= -github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/labstack/echo/v4 v4.5.0/go.mod h1:czIriw4a0C1dFun+ObrXp7ok03xON0N1awStJ6ArI7Y= github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k= github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= +github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA= +github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/maja42/goval v1.2.1 h1:fyEgzddqPgCZsKcFLk4C6SdCHyEaAHYvtZG4mGzQOHU= github.com/maja42/goval v1.2.1/go.mod h1:42LU+BQXL/veE9jnTTUOSj38GRmOTSThYSXRVodI5J4= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= @@ -348,6 +365,8 @@ github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5Vgl github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w= github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw= github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= @@ -837,8 +856,9 @@ google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQ gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/ini.v1 v1.51.1/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= @@ -854,6 +874,7 @@ gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20191120175047-4206685974f2/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/hrp/cmd/convert.go b/hrp/cmd/convert.go index 48a9f4bc..31c536e4 100644 --- a/hrp/cmd/convert.go +++ b/hrp/cmd/convert.go @@ -2,48 +2,61 @@ package cmd import ( "errors" - "os" - "github.com/rs/zerolog/log" "github.com/spf13/cobra" - "github.com/httprunner/httprunner/v4/hrp/internal/convert/case2script" + "github.com/httprunner/httprunner/v4/hrp/internal/convert" ) var convertCmd = &cobra.Command{ Use: "convert $path...", - Short: "convert JSON/YAML testcases to pytest/gotest scripts", - Args: cobra.ExactValidArgs(1), + Short: "convert external cases to JSON/YAML/gotest/pytest testcases", + Args: cobra.MinimumNArgs(1), PreRun: func(cmd *cobra.Command, args []string) { setLogLevel(logLevel) }, RunE: func(cmd *cobra.Command, args []string) error { - // TODO: integrate har2case, postman2case, etc. in convert command (forward compatibility) - if !pytestFlag && !gotestFlag { - return errors.New("please specify convertion type") + var flagCount int + var outputType convert.OutputType + if toJSONFlag { + flagCount++ } - - var err error - if gotestFlag { - err = case2script.Convert2TestScripts("gotest", args...) - } else { - err = case2script.Convert2TestScripts("pytest", args...) + if toYAMLFlag { + flagCount++ + outputType = convert.OutputTypeYAML } - if err != nil { - log.Error().Err(err).Msg("convert test scripts failed") - os.Exit(1) + if toGoTestFlag { + flagCount++ + outputType = convert.OutputTypeGoTest } + if toPyTestFlag { + flagCount++ + outputType = convert.OutputTypePyTest + } + if flagCount > 1 { + return errors.New("please specify at most one conversion flag") + } + iCaseConverters := convert.LoadConverters(outputType, outputDir, profilePath, args) + convert.Run(iCaseConverters) return nil }, } var ( - pytestFlag bool - gotestFlag bool + toJSONFlag bool + toYAMLFlag bool + toGoTestFlag bool + toPyTestFlag bool + outputDir string + profilePath string ) func init() { rootCmd.AddCommand(convertCmd) - convertCmd.Flags().BoolVar(&pytestFlag, "pytest", true, "convert to pytest scripts") - convertCmd.Flags().BoolVar(&gotestFlag, "gotest", false, "convert to gotest scripts (TODO)") + convertCmd.Flags().BoolVar(&toPyTestFlag, "to-pytest", false, "convert to pytest scripts") + convertCmd.Flags().BoolVar(&toGoTestFlag, "to-gotest", false, "convert to gotest scripts (TODO)") + convertCmd.Flags().BoolVar(&toJSONFlag, "to-json", false, "convert to JSON scripts (default)") + convertCmd.Flags().BoolVar(&toYAMLFlag, "to-yaml", false, "convert to YAML scripts") + convertCmd.Flags().StringVarP(&outputDir, "output-dir", "d", "", "specify output directory, default to the same dir with har file") + convertCmd.Flags().StringVarP(&profilePath, "profile", "p", "", "specify profile path to override headers (except for auto-generated headers) and cookies") } diff --git a/hrp/cmd/har2case.go b/hrp/cmd/har2case.go index 42fab1bd..d26fc4ff 100644 --- a/hrp/cmd/har2case.go +++ b/hrp/cmd/har2case.go @@ -40,11 +40,6 @@ var har2caseCmd = &cobra.Command{ har.SetProfile(har2caseProfilePath) } - // specify profile - if har2casePatchPath != "" { - har.SetPatch(har2casePatchPath) - } - // generate json/yaml files if har2caseGenYAMLFlag { outputPath, err = har.GenYAML() @@ -66,7 +61,6 @@ var ( har2caseGenYAMLFlag bool har2caseOutputDir string har2caseProfilePath string - har2casePatchPath string ) func init() { @@ -75,5 +69,4 @@ func init() { har2caseCmd.Flags().BoolVarP(&har2caseGenYAMLFlag, "to-yaml", "y", false, "convert to YAML format") har2caseCmd.Flags().StringVarP(&har2caseOutputDir, "output-dir", "d", "", "specify output directory, default to the same dir with har file") har2caseCmd.Flags().StringVarP(&har2caseProfilePath, "profile", "p", "", "specify profile path to override headers and cookies") - har2caseCmd.Flags().StringVarP(&har2casePatchPath, "patch", "r", "", "specify the path of the file used to replace headers and cookies") } diff --git a/hrp/cmd/postman2case.go b/hrp/cmd/postman2case.go deleted file mode 100644 index 2e0c1369..00000000 --- a/hrp/cmd/postman2case.go +++ /dev/null @@ -1,79 +0,0 @@ -package cmd - -import ( - "errors" - - "github.com/rs/zerolog/log" - "github.com/spf13/cobra" - - "github.com/httprunner/httprunner/v4/hrp/internal/convert/postman2case" -) - -// postman2caseCmd represents the postman2case command -var postman2caseCmd = &cobra.Command{ - Use: "postman2case $postman_path...", - Short: "convert postman collection to json/yaml testcase files", - Long: `convert postman collection to json/yaml testcase files`, - Args: cobra.MinimumNArgs(1), - PreRun: func(cmd *cobra.Command, args []string) { - setLogLevel(logLevel) - }, - RunE: func(cmd *cobra.Command, args []string) error { - var outputFiles []string - for _, arg := range args { - // must choose one - if !postman2caseGenJSONFlag && !postman2caseGenYAMLFlag { - return errors.New("please select convert format type") - } - var outputPath string - var err error - - collection := postman2case.NewCollection(arg) - - // specify output dir - if postman2caseOutputDir != "" { - collection.SetOutputDir(postman2caseOutputDir) - } - - // specify profile path - if postman2caseProfilePath != "" { - collection.SetProfile(postman2caseProfilePath) - } - - // specify patch path - if postman2casePatchPath != "" { - collection.SetPatch(postman2casePatchPath) - } - - // generate json/yaml files - if postman2caseGenYAMLFlag { - outputPath, err = collection.GenYAML() - } else { - outputPath, err = collection.GenJSON() // default - } - if err != nil { - return err - } - outputFiles = append(outputFiles, outputPath) - } - log.Info().Strs("output", outputFiles).Msg("convert testcase success") - return nil - }, -} - -var ( - postman2caseGenJSONFlag bool - postman2caseGenYAMLFlag bool - postman2caseOutputDir string - postman2caseProfilePath string - postman2casePatchPath string -) - -func init() { - rootCmd.AddCommand(postman2caseCmd) - postman2caseCmd.Flags().BoolVarP(&postman2caseGenJSONFlag, "to-json", "j", true, "convert to JSON format") - postman2caseCmd.Flags().BoolVarP(&postman2caseGenYAMLFlag, "to-yaml", "y", false, "convert to YAML format") - postman2caseCmd.Flags().StringVarP(&postman2caseOutputDir, "output-dir", "d", "", "specify output directory, default to the same dir with postman collection file") - postman2caseCmd.Flags().StringVarP(&postman2caseProfilePath, "profile", "p", "", "specify profile path to override original headers (except for Content-Type) and cookies") - postman2caseCmd.Flags().StringVarP(&postman2casePatchPath, "patch", "r", "", "specify patch path to create or update headers and cookies") -} diff --git a/hrp/internal/builtin/utils.go b/hrp/internal/builtin/utils.go index cacad024..d32adfde 100644 --- a/hrp/internal/builtin/utils.go +++ b/hrp/internal/builtin/utils.go @@ -285,7 +285,8 @@ func LoadFile(path string, structObj interface{}) (err error) { if err != nil { return errors.Wrap(err, "read file failed") } - + // remove BOM at the beginning of file + file = bytes.Trim(file, "\xef\xbb\xbf") ext := filepath.Ext(path) switch ext { case ".json", ".har": @@ -351,3 +352,9 @@ func readFile(path string) ([]byte, error) { } return file, nil } + +func GetOutputNameWithoutExtension(path string) string { + base := filepath.Base(path) + ext := filepath.Ext(base) + return base[0:len(base)-len(ext)] + "_test" +} diff --git a/hrp/internal/convert/README.md b/hrp/internal/convert/README.md new file mode 100644 index 00000000..474c8c0e --- /dev/null +++ b/hrp/internal/convert/README.md @@ -0,0 +1,68 @@ +# hrp convert + +## 快速上手 +```shell +$ hrp convert -h +convert external cases to JSON/YAML/gotest/pytest testcases + +Usage: + hrp convert $path... [flags] + +Flags: + -h, --help help for convert + -d, --output-dir string specify output directory, default to the same dir with har file + -p, --profile string specify profile path to override headers (except for auto-generated headers) and cookies + --to-gotest convert to gotest scripts (TODO) + --to-json convert to JSON scripts (default true) + --to-pytest convert to pytest scripts + --to-yaml convert to YAML scripts + +Global Flags: + --log-json set log to json format + -l, --log-level string set log level (default "INFO") +``` +`hrp convert` 指令用于将 HAR/Postman/JMeter/Swagger 等格式的外部脚本转化为 JSON/YAML/gotest/pytest 形态的测试用例,同时也支持测试用例各个形态之间的相互转化,输出的测试用例文件名格式为 `不带扩展名的原文件名称` + `_test` + `json/yaml/go/py` 后缀。 + +该指令的所有参数的详细介绍如下: + +1. `--to-json / --to-yaml / --to-gotest / --to-pytest` 用于将输入的外部脚本转化为对应形态的测试用例,四个参数中最多只能指定一个,如果不指定则默认会将输入转化为 JSON 形态的测试用例 +2. `--output-dir` 后接测试用例的期望输出目录的路径,用于将转换生成的测试用例输出到对应的文件夹 +3. `--profile` 后接 `profile` 配置文件的路径,目前支持替换(不存在则会创建)或者覆盖输入的外部脚本/测试用例中的 `Headers` 和 `Cookies` 信息,`profile` 文件的后缀可以为 `json/yaml/yml`,下面给出两类 `profile` 配置文件的示例: +- 根据 `profile` 替换指定的 `Headers` 和 `Cookies` 信息 +```yaml +headers: + Header1: "this header will be created or updated" +cookies: + Cookie1: "this cookie will be created or updated" + +``` +- 根据 `profile` 覆盖原有的 `Headers` 和 `Cookies` 信息 +```yaml +override: true +headers: + Header1: "all original headers will be overridden" +cookies: + Cookie1: "all original cookies will be overridden" +``` + +## 注意事项 +1. 指定 `override` 为 `false/true` 可以选择 `profile` 的修改模式为替换/覆盖。需要注意的是,如果不指定该字段则 `profile` 的默认修改模式为**替换**模式, +2. 输入为 JSON/YAML 测试用例时,良好兼容 Golang/Python 双引擎之间的差异(请求体、断言部分的格式略有不同),输出的 JSON/YAML 则统一采用 Golang 引擎的风格 + + +## 转换流程图 + +![flow chart](asset/flowgram.svg) + +## 开发进度 + +| from \ to | JSON | YAML | GoTest | PyTest | +|:---------:|:----:|:----:|:------:|:------:| +| HAR | ✅ | ✅ | ❌ | ✅ | +| Postman | ✅ | ✅ | ❌ | ✅ | +| JMeter | ❌ | ❌ | ❌ | ❌ | +| Swagger | ❌ | ❌ | ❌ | ❌ | +| JSON | ✅ | ✅ | ❌ | ✅ | +| YAML | ✅ | ✅ | ❌ | ✅ | +| GoTest | ❌ | ❌ | ❌ | ❌ | +| PyTest | ❌ | ❌ | ❌ | ❌ | \ No newline at end of file diff --git a/hrp/internal/convert/asset/flowgram.svg b/hrp/internal/convert/asset/flowgram.svg new file mode 100644 index 00000000..76652f6b --- /dev/null +++ b/hrp/internal/convert/asset/flowgram.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="626px" height="713px" viewBox="-0.5 -0.5 626 713"><defs><linearGradient x1="0%" y1="0%" x2="0%" y2="100%" id="mx-gradient-ffffff-1-ffffff-1-s-2886-0"><stop offset="0%" style="stop-color:#FFFFFF"/><stop offset="100%" style="stop-color:#ffffff"/></linearGradient></defs><g><rect x="72" y="536" width="480" height="160" rx="24" ry="24" fill="#cce5ff" stroke="none" pointer-events="all"/><path d="M 71.5 151 L 71.5 161 L 37 161 L 37 611 L 92.5 611 L 92.5 600.5 L 111.5 616 L 92.5 631.5 L 92.5 621 L 27 621 L 27 151 Z" fill="#ffffff" stroke="#000000" stroke-miterlimit="1.42" pointer-events="all"/><path d="M 92.5 611 L 92.5 600.5 L 111.5 616 L 92.5 631.5 L 92.5 621" fill="none" stroke="#000000" stroke-miterlimit="4" pointer-events="all"/><path d="M 552.5 161 L 552.5 151 L 597 151 L 597 621 L 531.5 621 L 531.5 631.5 L 512.5 616 L 531.5 600.5 L 531.5 611 L 587 611 L 587 161 Z" fill="#ffffff" stroke="#000000" stroke-miterlimit="1.42" pointer-events="all"/><path d="M 531.5 621 L 531.5 631.5 L 512.5 616 L 531.5 600.5 L 531.5 611" fill="none" stroke="#000000" stroke-miterlimit="4" pointer-events="all"/><rect x="72" y="16" width="480" height="280" rx="42" ry="42" fill="#e5ccff" stroke="none" pointer-events="all"/><rect x="72" y="336" width="480" height="160" rx="24" ry="24" fill="#ccffcc" stroke="none" pointer-events="all"/><rect x="112" y="56" width="160" height="80" rx="12" ry="12" fill="url(#mx-gradient-ffffff-1-ffffff-1-s-2886-0)" stroke="#000000" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 158px; height: 1px; padding-top: 96px; margin-left: 113px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 14px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; "><span style="color: rgb(37, 37, 37); font-family: Roboto, arial, sans-serif; text-align: start; font-size: 14px;"><font style="font-size: 14px;">HTTP 存档格式文件<br style="font-size: 14px;" />(.har)<br style="font-size: 14px;" /></font></span></div></div></div></foreignObject></g><rect x="352" y="56" width="160" height="80" rx="12" ry="12" fill="url(#mx-gradient-ffffff-1-ffffff-1-s-2886-0)" stroke="#000000" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 158px; height: 1px; padding-top: 96px; margin-left: 353px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 14px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; "><span style="color: rgb(37, 37, 37); font-family: Roboto, arial, sans-serif; text-align: start; font-size: 14px;"><font style="font-size: 14px;">Postman 项目文件<br style="font-size: 14px;" />(.json)<br style="font-size: 14px;" /></font></span></div></div></div></foreignObject></g><rect x="352" y="176" width="160" height="80" rx="12" ry="12" fill="url(#mx-gradient-ffffff-1-ffffff-1-s-2886-0)" stroke="#000000" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 158px; height: 1px; padding-top: 216px; margin-left: 353px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 14px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; "><span style="color: rgb(37, 37, 37); font-family: Roboto, arial, sans-serif; text-align: start; font-size: 14px;"><font style="font-size: 14px;">JMeter 项目文件<br style="font-size: 14px;" />(.jmx)<br style="font-size: 14px;" /></font></span></div></div></div></foreignObject></g><rect x="112" y="576" width="160" height="80" rx="12" ry="12" fill="url(#mx-gradient-ffffff-1-ffffff-1-s-2886-0)" stroke="#000000" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 158px; height: 1px; padding-top: 616px; margin-left: 113px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 14px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; ">gotest 测试用例<br style="font-size: 14px;" />(.go)</div></div></div></foreignObject></g><rect x="352" y="576" width="160" height="80" rx="12" ry="12" fill="url(#mx-gradient-ffffff-1-ffffff-1-s-2886-0)" stroke="#000000" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 158px; height: 1px; padding-top: 616px; margin-left: 353px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 14px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; ">pytest 测试用例<br style="font-size: 14px;" />(.py)</div></div></div></foreignObject></g><rect x="112" y="376" width="160" height="80" rx="12" ry="12" fill="url(#mx-gradient-ffffff-1-ffffff-1-s-2886-0)" stroke="#000000" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 158px; height: 1px; padding-top: 416px; margin-left: 113px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 14px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; ">JSON 测试用例<br style="font-size: 14px;" />(.json)</div></div></div></foreignObject></g><rect x="352" y="376" width="160" height="80" rx="12" ry="12" fill="url(#mx-gradient-ffffff-1-ffffff-1-s-2886-0)" stroke="#000000" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 158px; height: 1px; padding-top: 416px; margin-left: 353px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 14px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; ">YAML 测试用例<br style="font-size: 14px;" />(.yaml)</div></div></div></foreignObject></g><path d="M 437 556.5 L 447.5 556.5 L 432 575.5 L 416.5 556.5 L 427 556.5 L 427 475.5 L 416.5 475.5 L 432 456.5 L 447.5 475.5 L 437 475.5 Z" fill="#ffffff" stroke="#000000" stroke-linejoin="round" stroke-miterlimit="10" pointer-events="all"/><path d="M 286.98 562.55 L 295.71 568.37 L 272.28 575.58 L 269.92 551.18 L 278.66 557 L 337.02 469.45 L 328.29 463.63 L 351.72 456.42 L 354.08 480.82 L 345.34 475 Z" fill="#ffffff" stroke="#000000" stroke-linejoin="round" stroke-miterlimit="10" pointer-events="all"/><path d="M 345.34 557 L 354.08 551.18 L 351.72 575.58 L 328.29 568.37 L 337.02 562.55 L 278.66 475 L 269.92 480.82 L 272.28 456.42 L 295.71 463.63 L 286.98 469.45 Z" fill="#ffffff" stroke="#000000" stroke-linejoin="round" stroke-miterlimit="10" pointer-events="all"/><path d="M 187 296.5 L 197 296.5 L 197 356.5 L 207.5 356.5 L 192 375.5 L 176.5 356.5 L 187 356.5 Z" fill="#ffffff" stroke="#000000" stroke-linejoin="round" stroke-miterlimit="10" pointer-events="all"/><rect x="112" y="176" width="160" height="80" rx="12" ry="12" fill="url(#mx-gradient-ffffff-1-ffffff-1-s-2886-0)" stroke="#000000" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 158px; height: 1px; padding-top: 216px; margin-left: 113px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 14px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; "><span style="color: rgb(37, 37, 37); font-family: Roboto, arial, sans-serif; text-align: start; font-size: 14px;"><font style="font-size: 14px;">Swagger 脚本文件<br style="font-size: 14px;" />(.json / .yaml)<br style="font-size: 14px;" /></font></span></div></div></div></foreignObject></g><rect x="262" y="16" width="100" height="40" fill="none" stroke="none" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 98px; height: 1px; padding-top: 36px; margin-left: 263px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 16px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; font-weight: bold; white-space: normal; word-wrap: normal; ">外部脚本文件</div></div></div></foreignObject></g><rect x="230.75" y="336" width="162.5" height="40" fill="none" stroke="none" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 161px; height: 1px; padding-top: 356px; margin-left: 232px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 16px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; font-weight: bold; white-space: normal; word-wrap: normal; ">JSON/YAML 测试用例</div></div></div></foreignObject></g><rect x="247" y="656" width="130" height="40" fill="none" stroke="none" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 128px; height: 1px; padding-top: 676px; margin-left: 248px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 16px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; font-weight: bold; white-space: normal; word-wrap: normal; ">代码形态测试用例</div></div></div></foreignObject></g><path d="M 197 556.5 L 207.5 556.5 L 192 575.5 L 176.5 556.5 L 187 556.5 L 187 475.5 L 176.5 475.5 L 192 456.5 L 207.5 475.5 L 197 475.5 Z" fill="#ffffff" stroke="#000000" stroke-linejoin="round" stroke-miterlimit="10" pointer-events="all"/><path d="M 427 296.5 L 437 296.5 L 437 356.5 L 447.5 356.5 L 432 375.5 L 416.5 356.5 L 427 356.5 Z" fill="#ffffff" stroke="#000000" stroke-linejoin="round" stroke-miterlimit="10" pointer-events="all"/><path d="M 332.5 411 L 332.5 400.5 L 351.5 416 L 332.5 431.5 L 332.5 421 L 291.5 421 L 291.5 431.5 L 272.5 416 L 291.5 400.5 L 291.5 411 Z" fill="#ffffff" stroke="#000000" stroke-linejoin="round" stroke-miterlimit="10" pointer-events="all"/><path d="M 332.5 611 L 332.5 600.5 L 351.5 616 L 332.5 631.5 L 332.5 621 L 291.5 621 L 291.5 631.5 L 272.5 616 L 291.5 600.5 L 291.5 611 Z" fill="#ffffff" stroke="#000000" stroke-linejoin="round" stroke-miterlimit="10" pointer-events="all"/></g></svg> \ No newline at end of file diff --git a/hrp/internal/convert/case2script/main.go b/hrp/internal/convert/case2script/main.go deleted file mode 100644 index bfc75b27..00000000 --- a/hrp/internal/convert/case2script/main.go +++ /dev/null @@ -1,120 +0,0 @@ -package case2script - -import ( - _ "embed" - "fmt" - "os" - - "github.com/rs/zerolog/log" - - "github.com/httprunner/httprunner/v4/hrp" - "github.com/httprunner/httprunner/v4/hrp/internal/builtin" - "github.com/httprunner/httprunner/v4/hrp/internal/sdk" - "github.com/httprunner/httprunner/v4/hrp/internal/version" -) - -func Convert2TestScripts(destType string, paths ...string) error { - // report event - sdk.SendEvent(sdk.EventTracking{ - Category: "ConvertTests", - Action: fmt.Sprintf("hrp convert --%s", destType), - }) - - if destType == "gotest" { - return convert2GoTestScripts(paths...) - } else { - // default to pytest - return convert2PyTestScripts(paths...) - } -} - -func convert2PyTestScripts(paths ...string) error { - httprunner := fmt.Sprintf("httprunner>=%s", version.HttpRunnerMinVersion) - python3, err := builtin.EnsurePython3Venv(httprunner) - if err != nil { - return err - } - - args := append([]string{"-m", "httprunner", "make"}, paths...) - return builtin.ExecCommand(python3, args...) -} - -func convert2GoTestScripts(paths ...string) error { - log.Warn().Msg("convert to gotest scripts is not supported yet") - os.Exit(1) - - // TODO - var testCasePaths []hrp.ITestCase - for _, path := range paths { - testCasePath := hrp.TestCasePath(path) - testCasePaths = append(testCasePaths, &testCasePath) - } - - testCases, err := hrp.LoadTestCases(testCasePaths...) - if err != nil { - log.Error().Err(err).Msg("failed to load testcases") - return err - } - - var pytestPaths []string - for _, testCase := range testCases { - tc := testCase.ToTCase() - converter := CaseConverter{ - TCase: tc, - } - pytestPath, err := converter.ToPyTest() - if err != nil { - log.Error().Err(err). - Str("originPath", tc.Config.Path). - Msg("convert to pytest failed") - continue - } - log.Info(). - Str("pytestPath", pytestPath). - Str("originPath", tc.Config.Path). - Msg("convert to pytest success") - pytestPaths = append(pytestPaths, pytestPath) - } - - // format pytest scripts with black - python3, err := builtin.EnsurePython3Venv("black") - if err != nil { - return err - } - args := append([]string{"-m", "black"}, pytestPaths...) - return builtin.ExecCommand(python3, args...) -} - -//go:embed testcase.tmpl -var testcaseTemplate string - -type CaseConverter struct { - *hrp.TCase -} - -func (c *CaseConverter) ToPyTest() (string, error) { - script := convertConfig(c.TCase.Config) - println(script) - return script, nil -} - -func (c *CaseConverter) ToGoTest() (string, error) { - return "", nil -} - -func convertConfig(config *hrp.TConfig) string { - script := fmt.Sprintf("Config('%s')", config.Name) - - if config.Variables != nil { - script += fmt.Sprintf(".variables(**{%v})", config.Variables) - } - if config.BaseURL != "" { - script += fmt.Sprintf(".base_url('%s')", config.BaseURL) - } - if config.Export != nil { - script += fmt.Sprintf(".export(*%v)", config.Export) - } - script += fmt.Sprintf(".verify(%v)", config.Verify) - - return script -} diff --git a/hrp/internal/convert/converter.go b/hrp/internal/convert/converter.go new file mode 100644 index 00000000..ac6831cc --- /dev/null +++ b/hrp/internal/convert/converter.go @@ -0,0 +1,374 @@ +package convert + +import ( + _ "embed" + "fmt" + "os" + "path/filepath" + "reflect" + + "github.com/go-openapi/spec" + "github.com/pkg/errors" + "github.com/rs/zerolog/log" + + "github.com/httprunner/httprunner/v4/hrp" + "github.com/httprunner/httprunner/v4/hrp/internal/builtin" + "github.com/httprunner/httprunner/v4/hrp/internal/sdk" +) + +const ( + suffixJSON = ".json" + suffixYAML = ".yaml" + suffixGoTest = ".go" + suffixPyTest = ".py" +) + +type InputType int + +const ( + InputTypeUnknown InputType = iota // default input type: unknown + InputTypeHAR + InputTypePostman + InputTypeSwagger + InputTypeJMeter + InputTypeJSON + InputTypeYAML + InputTypeGoTest + InputTypePyTest +) + +func (inputType InputType) String() string { + switch inputType { + case InputTypeHAR: + return "har" + case InputTypePostman: + return "postman" + case InputTypeSwagger: + return "swagger" + case InputTypeJMeter: + return "jmeter" + case InputTypeJSON: + return "json testcase" + case InputTypeYAML: + return "yaml testcase" + case InputTypeGoTest: + return "gotest script" + case InputTypePyTest: + return "pytest script" + default: + return "unknown" + } +} + +type OutputType int + +const ( + OutputTypeJSON OutputType = iota // default output type: JSON + OutputTypeYAML + OutputTypeGoTest + OutputTypePyTest +) + +func (outputType OutputType) String() string { + switch outputType { + case OutputTypeYAML: + return "yaml" + case OutputTypeGoTest: + return "gotest" + case OutputTypePyTest: + return "pytest" + default: + return "json" + } +} + +// TCaseConverter holds the common properties of case converter +type TCaseConverter struct { + InputPath string + OutputDir string + Profile *Profile + InputType InputType + OutputType OutputType + CaseHAR *CaseHar + CasePostman *CasePostman + CaseSwagger *spec.Swagger + TCase *hrp.TCase +} + +// Profile is used to override or update(create if not existed) original headers and cookies +type Profile struct { + Override bool `json:"override" yaml:"override"` + Headers map[string]string `json:"headers" yaml:"headers"` + Cookies map[string]string `json:"cookies" yaml:"cookies"` +} + +func NewTCaseConverter(path string) (tCaseConverter *TCaseConverter) { + tCaseConverter = &TCaseConverter{ + InputPath: path, + InputType: InputTypeUnknown, + } + extName := filepath.Ext(path) + if extName == "" { + log.Warn().Msg("extension name should be specified") + return + } + var err error + switch extName { + case ".har": + caseHAR := new(CaseHar) + err = builtin.LoadFile(path, caseHAR) + if err == nil && !reflect.DeepEqual(*caseHAR, CaseHar{}) { + tCaseConverter.InputType = InputTypeHAR + tCaseConverter.CaseHAR = caseHAR + } + case ".json": + tCase := new(hrp.TCase) + err = builtin.LoadFile(path, tCase) + if err == nil && !reflect.DeepEqual(*tCase, hrp.TCase{}) { + tCaseConverter.InputType = InputTypeJSON + tCaseConverter.TCase = tCase + break + } + casePostman := new(CasePostman) + err = builtin.LoadFile(path, casePostman) + if err == nil && !reflect.DeepEqual(*casePostman, CasePostman{}) { + tCaseConverter.InputType = InputTypePostman + tCaseConverter.CasePostman = casePostman + break + } + caseSwagger := new(spec.Swagger) + err = builtin.LoadFile(path, caseSwagger) + if err == nil && !reflect.DeepEqual(*caseSwagger, spec.Swagger{}) { + tCaseConverter.InputType = InputTypeSwagger + tCaseConverter.CaseSwagger = caseSwagger + } + case ".yaml", ".yml": + tCase := new(hrp.TCase) + err = builtin.LoadFile(path, tCase) + if err == nil && !reflect.DeepEqual(*tCase, hrp.TCase{}) { + tCaseConverter.InputType = InputTypeYAML + tCaseConverter.TCase = tCase + break + } + caseSwagger := new(spec.Swagger) + err = builtin.LoadFile(path, caseSwagger) + if err == nil && !reflect.DeepEqual(*caseSwagger, spec.Swagger{}) { + tCaseConverter.InputType = InputTypeSwagger + tCaseConverter.CaseSwagger = caseSwagger + } + case ".go": // TODO + tCaseConverter.InputType = InputTypeGoTest + case ".py": // TODO + tCaseConverter.InputType = InputTypePyTest + case ".jmx": // TODO + tCaseConverter.InputType = InputTypeJMeter + default: + log.Warn(). + Str("input path", tCaseConverter.InputPath). + Msgf("unsupported file type: %v", extName) + } + if tCaseConverter.InputType != InputTypeUnknown { + log.Info(). + Str("input path", tCaseConverter.InputPath). + Msgf("load case as: %s", tCaseConverter.InputType.String()) + } else { + log.Error().Err(err). + Str("input path", tCaseConverter.InputPath). + Msgf("failed to load case") + } + return +} + +func (c *TCaseConverter) SetProfile(path string) { + log.Info().Str("input path", c.InputPath).Str("profile", path).Msg("set profile") + profile := new(Profile) + err := builtin.LoadFile(path, profile) + if err != nil { + log.Warn().Str("path", path). + Msg("failed to load profile, ignore!") + return + } + c.Profile = profile +} + +func (c *TCaseConverter) SetOutputDir(dir string) { + log.Info().Str("input path", c.InputPath).Str("output directory", dir).Msg("set output directory") + c.OutputDir = dir +} + +func (c *TCaseConverter) genOutputPath(suffix string) string { + outFileFullName := builtin.GetOutputNameWithoutExtension(c.InputPath) + suffix + if c.OutputDir != "" { + return filepath.Join(c.OutputDir, outFileFullName) + } else { + return filepath.Join(filepath.Dir(c.InputPath), outFileFullName) + } + // TODO avoid outFileFullName conflict? +} + +func (c *TCaseConverter) ToPyTest() (string, error) { + script := convertConfig(c.TCase.Config) + println(script) + return script, nil +} + +func convertConfig(config *hrp.TConfig) string { + script := fmt.Sprintf("Config('%s')", config.Name) + + if config.Variables != nil { + script += fmt.Sprintf(".variables(**{%v})", config.Variables) + } + if config.BaseURL != "" { + script += fmt.Sprintf(".base_url('%s')", config.BaseURL) + } + if config.Export != nil { + script += fmt.Sprintf(".export(*%v)", config.Export) + } + script += fmt.Sprintf(".verify(%v)", config.Verify) + + return script +} + +func (c *TCaseConverter) ToGoTest() (string, error) { + return "", nil +} + +// ICaseConverter represents all kinds of case converters which could convert case into JSON/YAML/gotest/pytest format +type ICaseConverter interface { + Struct() *TCaseConverter + ToJSON() (string, error) + ToJSONTemp() (string, error) + ToYAML() (string, error) + ToGoTest() (string, error) + ToPyTest() (string, error) +} + +func LoadConverters(outputType OutputType, outputDir, profilePath string, args []string) []ICaseConverter { + // report event + sdk.SendEvent(sdk.EventTracking{ + Category: "ConvertTests", + Action: fmt.Sprintf("hrp convert --to-%s", outputType.String()), + }) + + var iCaseConverters []ICaseConverter + for _, arg := range args { + tCaseConverter := NewTCaseConverter(arg) + tCaseConverter.OutputType = outputType + if outputDir != "" { + tCaseConverter.SetOutputDir(outputDir) + } + if profilePath != "" { + tCaseConverter.SetProfile(profilePath) + } + switch tCaseConverter.InputType { + case InputTypeHAR: + iCaseConverters = append(iCaseConverters, NewConverterHAR(tCaseConverter)) + case InputTypePostman: + iCaseConverters = append(iCaseConverters, NewConverterPostman(tCaseConverter)) + case InputTypeJSON: + iCaseConverters = append(iCaseConverters, NewConverterJSON(tCaseConverter)) + case InputTypeYAML: + iCaseConverters = append(iCaseConverters, NewConverterYAML(tCaseConverter)) + case InputTypeSwagger, InputTypeJMeter, InputTypeGoTest, InputTypePyTest: + log.Warn(). + Str("input path", tCaseConverter.InputPath). + Msg("case type not supported yet, ignore!") + default: + log.Warn(). + Str("input path", tCaseConverter.InputPath). + Msg("unknown case type, ignore!") + } + } + return iCaseConverters +} + +func Run(iCaseConverters []ICaseConverter) { + var outputFiles []string + var err error + for _, iCaseConverter := range iCaseConverters { + log.Info().Str("input path", iCaseConverter.Struct().InputPath).Msg("start converting") + var outputFile string + switch iCaseConverter.Struct().OutputType { + case OutputTypeYAML: + outputFile, err = iCaseConverter.ToYAML() + case OutputTypeGoTest: + outputFile, err = iCaseConverter.ToGoTest() + case OutputTypePyTest: + outputFile, err = iCaseConverter.ToPyTest() + default: + outputFile, err = iCaseConverter.ToJSON() + } + if err != nil { + log.Error().Err(err). + Str("input path", iCaseConverter.Struct().InputPath). + Msg("error occurs during converting") + continue + } + outputFiles = append(outputFiles, outputFile) + } + log.Info().Strs("output files", outputFiles).Msg("conversion completed") +} + +func makeTestCaseFromJSONYAML(iCaseConverter ICaseConverter) (*hrp.TCase, error) { + tCase := iCaseConverter.Struct().TCase + if tCase == nil { + return nil, errors.Errorf("empty json/yaml testcase occurs") + } + profile := iCaseConverter.Struct().Profile + if profile == nil { + return tCase, nil + } + for _, step := range tCase.TestSteps { + // override original headers and cookies + if profile.Override { + step.Request.Headers = make(map[string]string) + step.Request.Cookies = make(map[string]string) + } + // update (create if not existed) original headers and cookies + if step.Request.Headers == nil { + step.Request.Headers = make(map[string]string) + } + if step.Request.Cookies == nil { + step.Request.Cookies = make(map[string]string) + } + for k, v := range profile.Headers { + step.Request.Headers[k] = v + } + for k, v := range profile.Cookies { + step.Request.Cookies[k] = v + } + } + return tCase, nil +} + +func convertToPyTest(iCaseConverter ICaseConverter) (string, error) { + // convert to temporary json testcase compatible with python engine style + jsonPath, err := iCaseConverter.ToJSONTemp() + inputType := iCaseConverter.Struct().InputType + if err != nil { + return "", errors.Wrapf(err, "(%s -> pytest step 1) failed to convert to temporary json testcase", inputType.String()) + } + defer func() { + if jsonPath != "" { + if err = os.Remove(jsonPath); err != nil { + log.Error().Err(err).Msgf("(%s -> pytest step defer) failed to clean temporary json testcase", inputType.String()) + } + } + }() + + // convert from temporary json testcase to pytest + converterJSON := NewConverterJSON(NewTCaseConverter(jsonPath)) + pyTestPath, err := converterJSON.MakePyTestScript() + if err != nil { + return "", errors.Wrap(err, "(json -> pytest step 2) failed to convert from temporary json testcase to pytest ") + } + + // rename resultant pytest + renamedPyTestPath := iCaseConverter.Struct().genOutputPath(suffixPyTest) + err = os.Rename(pyTestPath, renamedPyTestPath) + if err != nil { + log.Error().Err(err).Msg("(json -> pytest step 3) failed to rename the resultant pytest file") + return pyTestPath, nil + } + return renamedPyTestPath, nil +} diff --git a/hrp/internal/convert/converter_gotest.go b/hrp/internal/convert/converter_gotest.go new file mode 100644 index 00000000..863da231 --- /dev/null +++ b/hrp/internal/convert/converter_gotest.go @@ -0,0 +1,60 @@ +package convert + +import ( + _ "embed" + "os" + + "github.com/rs/zerolog/log" + + "github.com/httprunner/httprunner/v4/hrp" + "github.com/httprunner/httprunner/v4/hrp/internal/builtin" +) + +func convert2GoTestScripts(paths ...string) error { + log.Warn().Msg("convert to gotest scripts is not supported yet") + os.Exit(1) + + // TODO + var testCasePaths []hrp.ITestCase + for _, path := range paths { + testCasePath := hrp.TestCasePath(path) + testCasePaths = append(testCasePaths, &testCasePath) + } + + testCases, err := hrp.LoadTestCases(testCasePaths...) + if err != nil { + log.Error().Err(err).Msg("failed to load testcases") + return err + } + + var pytestPaths []string + for _, testCase := range testCases { + tc := testCase.ToTCase() + converter := TCaseConverter{ + TCase: tc, + } + pytestPath, err := converter.ToPyTest() + if err != nil { + log.Error().Err(err). + Str("originPath", tc.Config.Path). + Msg("convert to pytest failed") + continue + } + log.Info(). + Str("pytestPath", pytestPath). + Str("originPath", tc.Config.Path). + Msg("convert to pytest success") + pytestPaths = append(pytestPaths, pytestPath) + } + + // format pytest scripts with black + python3, err := builtin.EnsurePython3Venv("black") + if err != nil { + return err + } + args := append([]string{"-m", "black"}, pytestPaths...) + return builtin.ExecCommand(python3, args...) +} + +//go:embed testcase.tmpl +var testcaseTemplate string diff --git a/hrp/internal/convert/converter_har.go b/hrp/internal/convert/converter_har.go new file mode 100644 index 00000000..d34717c9 --- /dev/null +++ b/hrp/internal/convert/converter_har.go @@ -0,0 +1,716 @@ +package convert + +import ( + "encoding/base64" + "fmt" + "github.com/httprunner/httprunner/v4/hrp/internal/builtin" + "net/url" + "sort" + "strings" + "time" + + "github.com/pkg/errors" + "github.com/rs/zerolog/log" + + "github.com/httprunner/httprunner/v4/hrp" + "github.com/httprunner/httprunner/v4/hrp/internal/json" +) + +// ==================== model definition starts here ==================== + +/* +HTTP Archive (HAR) format +https://w3c.github.io/web-performance/specs/HAR/Overview.html +this file is copied from https://github.com/mrichman/hargo/blob/master/types.go +*/ + +// CaseHar is a container type for deserialization +type CaseHar struct { + Log Log `json:"log"` +} + +// Log represents the root of the exported data. This object MUST be present and its name MUST be "log". +type Log struct { + // The object contains the following name/value pairs: + + // Required. Version number of the format. + Version string `json:"version"` + // Required. An object of type creator that contains the name and version + // information of the log creator application. + Creator Creator `json:"creator"` + // Optional. An object of type browser that contains the name and version + // information of the user agent. + Browser Browser `json:"browser"` + // Optional. An array of objects of type page, each representing one exported + // (tracked) page. Leave out this field if the application does not support + // grouping by pages. + Pages []Page `json:"pages,omitempty"` + // Required. An array of objects of type entry, each representing one + // exported (tracked) HTTP request. + Entries []Entry `json:"entries"` + // Optional. A comment provided by the user or the application. Sorting + // entries by startedDateTime (starting from the oldest) is preferred way how + // to export data since it can make importing faster. However the reader + // application should always make sure the array is sorted (if required for + // the import). + Comment string `json:"comment"` +} + +// Creator contains information about the log creator application +type Creator struct { + // Required. The name of the application that created the log. + Name string `json:"name"` + // Required. The version number of the application that created the log. + Version string `json:"version"` + // Optional. A comment provided by the user or the application. + Comment string `json:"comment,omitempty"` +} + +// Browser that created the log +type Browser struct { + // Required. The name of the browser that created the log. + Name string `json:"name"` + // Required. The version number of the browser that created the log. + Version string `json:"version"` + // Optional. A comment provided by the user or the browser. + Comment string `json:"comment"` +} + +// Page object for every exported web page and one <entry> object for every HTTP request. +// In case when an HTTP trace tool isn't able to group requests by a page, +// the <pages> object is empty and individual requests doesn't have a parent page. +type Page struct { + /* There is one <page> object for every exported web page and one <entry> + object for every HTTP request. In case when an HTTP trace tool isn't able to + group requests by a page, the <pages> object is empty and individual + requests doesn't have a parent page. + */ + + // Date and time stamp for the beginning of the page load + // (ISO 8601 YYYY-MM-DDThh:mm:ss.sTZD, e.g. 2009-07-24T19:20:30.45+01:00). + StartedDateTime string `json:"startedDateTime"` + // Unique identifier of a page within the . Entries use it to refer the parent page. + ID string `json:"id"` + // Page title. + Title string `json:"title"` + // Detailed timing info about page load. + PageTiming PageTiming `json:"pageTiming"` + // (new in 1.2) A comment provided by the user or the application. + Comment string `json:"comment,omitempty"` +} + +// PageTiming describes timings for various events (states) fired during the page load. +// All times are specified in milliseconds. If a time info is not available appropriate field is set to -1. +type PageTiming struct { + // Content of the page loaded. Number of milliseconds since page load started + // (page.startedDateTime). Use -1 if the timing does not apply to the current + // request. + // Depeding on the browser, onContentLoad property represents DOMContentLoad + // event or document.readyState == interactive. + OnContentLoad int `json:"onContentLoad"` + // Page is loaded (onLoad event fired). Number of milliseconds since page + // load started (page.startedDateTime). Use -1 if the timing does not apply + // to the current request. + OnLoad int `json:"onLoad"` + // (new in 1.2) A comment provided by the user or the application. + Comment string `json:"comment"` +} + +// Entry is a unique, optional Reference to the parent page. +// Leave out this field if the application does not support grouping by pages. +type Entry struct { + Pageref string `json:"pageref,omitempty"` + // Date and time stamp of the request start + // (ISO 8601 YYYY-MM-DDThh:mm:ss.sTZD). + StartedDateTime string `json:"startedDateTime"` + // Total elapsed time of the request in milliseconds. This is the sum of all + // timings available in the timings object (i.e. not including -1 values) . + Time float32 `json:"time"` + // Detailed info about the request. + Request Request `json:"request"` + // Detailed info about the response. + Response Response `json:"response"` + // Info about cache usage. + Cache Cache `json:"cache"` + // Detailed timing info about request/response round trip. + PageTimings PageTimings `json:"pageTimings"` + // optional (new in 1.2) IP address of the server that was connected + // (result of DNS resolution). + ServerIPAddress string `json:"serverIPAddress,omitempty"` + // optional (new in 1.2) Unique ID of the parent TCP/IP connection, can be + // the client port number. Note that a port number doesn't have to be unique + // identifier in cases where the port is shared for more connections. If the + // port isn't available for the application, any other unique connection ID + // can be used instead (e.g. connection index). Leave out this field if the + // application doesn't support this info. + Connection string `json:"connection,omitempty"` + // (new in 1.2) A comment provided by the user or the application. + Comment string `json:"comment,omitempty"` +} + +// Request contains detailed info about performed request. +type Request struct { + // Request method (GET, POST, ...). + Method string `json:"method"` + // Absolute URL of the request (fragments are not included). + URL string `json:"url"` + // Request HTTP Version. + HTTPVersion string `json:"httpVersion"` + // List of cookie objects. + Cookies []Cookie `json:"cookies"` + // List of header objects. + Headers []NVP `json:"headers"` + // List of query parameter objects. + QueryString []NVP `json:"queryString"` + // Posted data. + PostData PostData `json:"postData"` + // Total number of bytes from the start of the HTTP request message until + // (and including) the double CRLF before the body. Set to -1 if the info + // is not available. + HeaderSize int `json:"headerSize"` + // Size of the request body (POST data payload) in bytes. Set to -1 if the + // info is not available. + BodySize int `json:"bodySize"` + // (new in 1.2) A comment provided by the user or the application. + Comment string `json:"comment"` +} + +// Response contains detailed info about the response. +type Response struct { + // Response status. + Status int `json:"status"` + // Response status description. + StatusText string `json:"statusText"` + // Response HTTP Version. + HTTPVersion string `json:"httpVersion"` + // List of cookie objects. + Cookies []Cookie `json:"cookies"` + // List of header objects. + Headers []NVP `json:"headers"` + // Details about the response body. + Content Content `json:"content"` + // Redirection target URL from the Location response header. + RedirectURL string `json:"redirectURL"` + // Total number of bytes from the start of the HTTP response message until + // (and including) the double CRLF before the body. Set to -1 if the info is + // not available. + // The size of received response-headers is computed only from headers that + // are really received from the server. Additional headers appended by the + // browser are not included in this number, but they appear in the list of + // header objects. + HeadersSize int `json:"headersSize"` + // Size of the received response body in bytes. Set to zero in case of + // responses coming from the cache (304). Set to -1 if the info is not + // available. + BodySize int `json:"bodySize"` + // optional (new in 1.2) A comment provided by the user or the application. + Comment string `json:"comment,omitempty"` +} + +// Cookie contains list of all cookies (used in <request> and <response> objects). +type Cookie struct { + // The name of the cookie. + Name string `json:"name"` + // The cookie value. + Value string `json:"value"` + // optional The path pertaining to the cookie. + Path string `json:"path,omitempty"` + // optional The host of the cookie. + Domain string `json:"domain,omitempty"` + // optional Cookie expiration time. + // (ISO 8601 YYYY-MM-DDThh:mm:ss.sTZD, e.g. 2009-07-24T19:20:30.123+02:00). + Expires string `json:"expires,omitempty"` + // optional Set to true if the cookie is HTTP only, false otherwise. + HTTPOnly bool `json:"httpOnly,omitempty"` + // optional (new in 1.2) True if the cookie was transmitted over ssl, false + // otherwise. + Secure bool `json:"secure,omitempty"` + // optional (new in 1.2) A comment provided by the user or the application. + Comment bool `json:"comment,omitempty"` +} + +// NVP is simply a name/value pair with a comment +type NVP struct { + Name string `json:"name"` + Value string `json:"value"` + Comment string `json:"comment,omitempty"` +} + +// PostData describes posted data, if any (embedded in <request> object). +type PostData struct { + // Mime type of posted data. + MimeType string `json:"mimeType"` + // List of posted parameters (in case of URL encoded parameters). + Params []PostParam `json:"params"` + // Plain text posted data + Text string `json:"text"` + // optional (new in 1.2) A comment provided by the user or the + // application. + Comment string `json:"comment,omitempty"` +} + +// PostParam is a list of posted parameters, if any (embedded in <postData> object). +type PostParam struct { + // name of a posted parameter. + Name string `json:"name"` + // optional value of a posted parameter or content of a posted file. + Value string `json:"value,omitempty"` + // optional name of a posted file. + FileName string `json:"fileName,omitempty"` + // optional content type of a posted file. + ContentType string `json:"contentType,omitempty"` + // optional (new in 1.2) A comment provided by the user or the application. + Comment string `json:"comment,omitempty"` +} + +// Content describes details about response content (embedded in <response> object). +type Content struct { + // Length of the returned content in bytes. Should be equal to + // response.bodySize if there is no compression and bigger when the content + // has been compressed. + Size int `json:"size"` + // optional Number of bytes saved. Leave out this field if the information + // is not available. + Compression int `json:"compression,omitempty"` + // MIME type of the response text (value of the Content-Type response + // header). The charset attribute of the MIME type is included (if + // available). + MimeType string `json:"mimeType"` + // optional Response body sent from the server or loaded from the browser + // cache. This field is populated with textual content only. The text field + // is either HTTP decoded text or a encoded (e.g. "base64") representation of + // the response body. Leave out this field if the information is not + // available. + Text string `json:"text,omitempty"` + // optional (new in 1.2) Encoding used for response text field e.g + // "base64". Leave out this field if the text field is HTTP decoded + // (decompressed & unchunked), than trans-coded from its original character + // set into UTF-8. + Encoding string `json:"encoding,omitempty"` + // optional (new in 1.2) A comment provided by the user or the application. + Comment string `json:"comment,omitempty"` +} + +// Cache contains info about a request coming from browser cache. +type Cache struct { + // optional State of a cache entry before the request. Leave out this field + // if the information is not available. + BeforeRequest CacheObject `json:"beforeRequest,omitempty"` + // optional State of a cache entry after the request. Leave out this field if + // the information is not available. + AfterRequest CacheObject `json:"afterRequest,omitempty"` + // optional (new in 1.2) A comment provided by the user or the application. + Comment string `json:"comment,omitempty"` +} + +// CacheObject is used by both beforeRequest and afterRequest +type CacheObject struct { + // optional - Expiration time of the cache entry. + Expires string `json:"expires,omitempty"` + // The last time the cache entry was opened. + LastAccess string `json:"lastAccess"` + // Etag + ETag string `json:"eTag"` + // The number of times the cache entry has been opened. + HitCount int `json:"hitCount"` + // optional (new in 1.2) A comment provided by the user or the application. + Comment string `json:"comment,omitempty"` +} + +// PageTimings describes various phases within request-response round trip. +// All times are specified in milliseconds. +type PageTimings struct { + Blocked int `json:"blocked,omitempty"` + // optional - Time spent in a queue waiting for a network connection. Use -1 + // if the timing does not apply to the current request. + DNS int `json:"dns,omitempty"` + // optional - DNS resolution time. The time required to resolve a host name. + // Use -1 if the timing does not apply to the current request. + Connect int `json:"connect,omitempty"` + // optional - Time required to create TCP connection. Use -1 if the timing + // does not apply to the current request. + Send int `json:"send"` + // Time required to send HTTP request to the server. + Wait int `json:"wait"` + // Waiting for a response from the server. + Receive int `json:"receive"` + // Time required to read entire response from the server (or cache). + Ssl int `json:"ssl,omitempty"` + // optional (new in 1.2) - Time required for SSL/TLS negotiation. If this + // field is defined then the time is also included in the connect field (to + // ensure backward compatibility with HAR 1.1). Use -1 if the timing does not + // apply to the current request. + Comment string `json:"comment,omitempty"` + // optional (new in 1.2) - A comment provided by the user or the application. +} + +// TestResult contains results for an individual HTTP request +type TestResult struct { + URL string `json:"url"` + Status int `json:"status"` // 200, 500, etc. + StartTime time.Time `json:"startTime"` + EndTime time.Time `json:"endTime"` + Latency int `json:"latency"` // milliseconds + Method string `json:"method"` + HarFile string `json:"harfile"` +} + +// ==================== model definition ends here ==================== + +func NewConverterHAR(converter *TCaseConverter) *ConverterHAR { + return &ConverterHAR{ + converter: converter, + } +} + +type ConverterHAR struct { + converter *TCaseConverter +} + +func (c *ConverterHAR) Struct() *TCaseConverter { + return c.converter +} + +func (c *ConverterHAR) ToJSON() (string, error) { + tCase, err := c.makeTestCase() + if err != nil { + return "", err + } + jsonPath := c.converter.genOutputPath(suffixJSON) + err = builtin.Dump2JSON(tCase, jsonPath) + if err != nil { + return "", err + } + return jsonPath, nil +} + +func (c *ConverterHAR) ToJSONTemp() (string, error) { + tCase, err := c.makeTestCaseTemp() + if err != nil { + return "", err + } + jsonPath := c.converter.genOutputPath(suffixJSON) + err = builtin.Dump2JSON(tCase, jsonPath) + if err != nil { + return "", err + } + return jsonPath, nil +} + +func (c *ConverterHAR) ToYAML() (string, error) { + tCase, err := c.makeTestCase() + if err != nil { + return "", err + } + yamlPath := c.converter.genOutputPath(suffixYAML) + err = builtin.Dump2YAML(tCase, yamlPath) + if err != nil { + return "", err + } + return yamlPath, nil +} + +func (c *ConverterHAR) ToGoTest() (string, error) { + //TODO implement me + return "", errors.New("convert from har to gotest scripts is not supported yet") +} + +func (c *ConverterHAR) ToPyTest() (string, error) { + return convertToPyTest(c) +} + +func (c *ConverterHAR) makeTestCase() (*hrp.TCase, error) { + teststeps, err := c.prepareTestSteps() + if err != nil { + return nil, err + } + + tCase := &hrp.TCase{ + Config: c.prepareConfig(), + TestSteps: teststeps, + } + err = tCase.MakeCompat2GoEngine() + if err != nil { + return nil, err + } + return tCase, nil +} + +func (c *ConverterHAR) makeTestCaseTemp() (*hrp.TCase, error) { + teststeps, err := c.prepareTestSteps() + if err != nil { + return nil, err + } + + tCase := &hrp.TCase{ + Config: c.prepareConfig(), + TestSteps: teststeps, + } + err = tCase.MakeCompat2PyEngine() + if err != nil { + return nil, err + } + return tCase, nil +} + +func (c *ConverterHAR) load() (*CaseHar, error) { + har := c.converter.CaseHAR + if har == nil { + return nil, errors.New("empty har case occurs") + } + return har, nil +} + +func (c *ConverterHAR) prepareConfig() *hrp.TConfig { + return hrp.NewConfig("testcase description"). + SetVerifySSL(false) +} + +func (c *ConverterHAR) prepareTestSteps() ([]*hrp.TStep, error) { + har, err := c.load() + if err != nil { + return nil, err + } + + var steps []*hrp.TStep + for _, entry := range har.Log.Entries { + step, err := c.prepareTestStep(&entry) + if err != nil { + return nil, err + } + steps = append(steps, step) + } + + return steps, nil +} + +func (c *ConverterHAR) prepareTestStep(entry *Entry) (*hrp.TStep, error) { + log.Info(). + Str("method", entry.Request.Method). + Str("url", entry.Request.URL). + Msg("convert teststep") + + step := &stepFromHAR{ + TStep: hrp.TStep{ + Request: &hrp.Request{}, + Validators: make([]interface{}, 0), + }, + profile: c.converter.Profile, + } + if err := step.makeRequestMethod(entry); err != nil { + return nil, err + } + if err := step.makeRequestURL(entry); err != nil { + return nil, err + } + if err := step.makeRequestParams(entry); err != nil { + return nil, err + } + if err := step.makeRequestCookies(entry); err != nil { + return nil, err + } + if err := step.makeRequestHeaders(entry); err != nil { + return nil, err + } + if err := step.makeRequestBody(entry); err != nil { + return nil, err + } + if err := step.makeValidate(entry); err != nil { + return nil, err + } + return &step.TStep, nil +} + +type stepFromHAR struct { + hrp.TStep + profile *Profile +} + +func (s *stepFromHAR) makeRequestMethod(entry *Entry) error { + s.Request.Method = hrp.HTTPMethod(entry.Request.Method) + return nil +} + +func (s *stepFromHAR) makeRequestURL(entry *Entry) error { + u, err := url.Parse(entry.Request.URL) + if err != nil { + log.Error().Err(err).Msg("make request url failed") + return err + } + s.Request.URL = fmt.Sprintf("%s://%s", u.Scheme, u.Host+u.Path) + return nil +} + +func (s *stepFromHAR) makeRequestParams(entry *Entry) error { + s.Request.Params = make(map[string]interface{}) + for _, param := range entry.Request.QueryString { + s.Request.Params[param.Name] = param.Value + } + return nil +} + +func (s *stepFromHAR) makeRequestCookies(entry *Entry) error { + // use cookies from har + s.Request.Cookies = make(map[string]string) + for _, cookie := range entry.Request.Cookies { + s.Request.Cookies[cookie.Name] = cookie.Value + } + + if s.profile == nil { + return nil + } + // override all cookies according to the profile + if s.profile.Override { + s.Request.Cookies = make(map[string]string) + } + // create or update the cookies according to the profile + for k, v := range s.profile.Cookies { + s.Request.Cookies[k] = v + } + return nil +} + +func (s *stepFromHAR) makeRequestHeaders(entry *Entry) error { + // use headers from har + s.Request.Headers = make(map[string]string) + for _, header := range entry.Request.Headers { + if strings.EqualFold(header.Name, "cookie") { + continue + } + s.Request.Headers[header.Name] = header.Value + } + + if s.profile == nil { + return nil + } + // override all headers according to the profile + if s.profile.Override { + s.Request.Headers = make(map[string]string) + } + // create or update the headers according to the profile + for k, v := range s.profile.Headers { + s.Request.Headers[k] = v + } + return nil +} + +func (s *stepFromHAR) makeRequestBody(entry *Entry) error { + mimeType := entry.Request.PostData.MimeType + if mimeType == "" { + // GET/HEAD/DELETE without body + return nil + } + + // POST/PUT with body + if strings.HasPrefix(mimeType, "application/json") { + // post json + var body interface{} + if entry.Request.PostData.Text == "" { + body = nil + } else { + err := json.Unmarshal([]byte(entry.Request.PostData.Text), &body) + if err != nil { + log.Error().Err(err).Msg("make request body failed") + return err + } + } + s.Request.Body = body + } else if strings.HasPrefix(mimeType, "application/x-www-form-urlencoded") { + // post form + var paramsList []string + for _, param := range entry.Request.PostData.Params { + paramsList = append(paramsList, fmt.Sprintf("%s=%s", param.Name, param.Value)) + } + s.Request.Body = strings.Join(paramsList, "&") + } else if strings.HasPrefix(mimeType, "text/plain") { + // post raw data + s.Request.Body = entry.Request.PostData.Text + } else { + // TODO + log.Error().Msgf("makeRequestBody: Not implemented for mimeType %s", mimeType) + } + return nil +} + +func (s *stepFromHAR) makeValidate(entry *Entry) error { + // make validator for response status code + s.Validators = append(s.Validators, hrp.Validator{ + Check: "status_code", + Assert: "equals", + Expect: entry.Response.Status, + Message: "assert response status code", + }) + + // make validators for response headers + for _, header := range entry.Response.Headers { + // assert Content-Type + if strings.EqualFold(header.Name, "Content-Type") { + s.Validators = append(s.Validators, hrp.Validator{ + Check: "headers.\"Content-Type\"", + Assert: "equals", + Expect: header.Value, + Message: "assert response header Content-Type", + }) + } + } + + // make validators for response body + respBody := entry.Response.Content + if respBody.Text == "" { + // response body is empty + return nil + } + if strings.HasPrefix(respBody.MimeType, "application/json") { + var data []byte + var err error + // response body is json + if respBody.Encoding == "base64" { + // decode base64 text + data, err = base64.StdEncoding.DecodeString(respBody.Text) + if err != nil { + return errors.Wrap(err, "decode base64 error") + } + } else if respBody.Encoding == "" { + // no encoding + data = []byte(respBody.Text) + } else { + // other encoding type + return nil + } + // convert to json + var body interface{} + if err = json.Unmarshal(data, &body); err != nil { + return errors.Wrap(err, "json.Unmarshal body error") + } + jsonBody, ok := body.(map[string]interface{}) + if !ok { + return fmt.Errorf("response body is not json, not matched with MimeType") + } + + // response body is json + keys := make([]string, 0, len(jsonBody)) + for k := range jsonBody { + keys = append(keys, k) + } + // sort map keys to keep validators in stable order + sort.Strings(keys) + for _, key := range keys { + value := jsonBody[key] + switch v := value.(type) { + case map[string]interface{}: + continue + case []interface{}: + continue + default: + s.Validators = append(s.Validators, hrp.Validator{ + Check: fmt.Sprintf("body.%s", key), + Assert: "equals", + Expect: v, + Message: fmt.Sprintf("assert response body %s", key), + }) + } + } + } + + return nil +} diff --git a/hrp/internal/convert/converter_har_test.go b/hrp/internal/convert/converter_har_test.go new file mode 100644 index 00000000..0d4daa11 --- /dev/null +++ b/hrp/internal/convert/converter_har_test.go @@ -0,0 +1,373 @@ +package convert + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/httprunner/httprunner/v4/hrp" +) + +var ( + harPath = "../../../examples/data/har/demo.har" + harPath2 = "../../../examples/data/har/postman-echo.har" + harProfileOverridePath = "../../../examples/data/har/profile_override.yml" +) + +var converterHAR = NewConverterHAR(NewTCaseConverter(harPath)) +var converterHAR2 = NewConverterHAR(NewTCaseConverter(harPath2)) + +func TestHAR2JSON(t *testing.T) { + jsonPath, err := converterHAR.ToJSON() + if !assert.NoError(t, err) { + t.Fatal() + } + if !assert.NotEmpty(t, jsonPath) { + t.Fatal() + } +} + +func TestHAR2YAML(t *testing.T) { + yamlPath, err := converterHAR2.ToYAML() + if !assert.NoError(t, err) { + t.Fatal() + } + if !assert.NotEmpty(t, yamlPath) { + t.Fatal() + } +} + +func TestLoadHAR(t *testing.T) { + h, err := converterHAR.load() + if !assert.NoError(t, err) { + t.Fatal() + } + if !assert.Equal(t, "GET", h.Log.Entries[0].Request.Method) { + t.Fatal() + } + if !assert.Equal(t, "POST", h.Log.Entries[1].Request.Method) { + t.Fatal() + } +} + +func TestLoadHARWithProfile(t *testing.T) { + tCaseConverter := NewTCaseConverter(harPath) + tCaseConverter.SetProfile(harProfileOverridePath) + h := NewConverterHAR(tCaseConverter) + _, err := h.load() + if !assert.NoError(t, err) { + t.Fatal() + } + + if !assert.Equal(t, + map[string]string{"Content-Type": "application/x-www-form-urlencoded"}, + h.converter.Profile.Headers) { + t.Fatal() + } + if !assert.Equal(t, + map[string]string{"UserName": "debugtalk"}, + h.converter.Profile.Cookies) { + t.Fatal() + } +} + +func TestMakeTestCaseFromHAR(t *testing.T) { + tCase, err := converterHAR.makeTestCase() + if !assert.NoError(t, err) { + t.Fatal() + } + + // make request method + if !assert.EqualValues(t, "GET", tCase.TestSteps[0].Request.Method) { + t.Fatal() + } + if !assert.EqualValues(t, "POST", tCase.TestSteps[1].Request.Method) { + t.Fatal() + } + + // make request url + if !assert.Equal(t, "https://postman-echo.com/get", tCase.TestSteps[0].Request.URL) { + t.Fatal() + } + if !assert.Equal(t, "https://postman-echo.com/post", tCase.TestSteps[1].Request.URL) { + t.Fatal() + } + + // make request params + if !assert.Equal(t, "HDnY8", tCase.TestSteps[0].Request.Params["foo1"]) { + t.Fatal() + } + + // make request cookies + if !assert.NotEmpty(t, tCase.TestSteps[1].Request.Cookies["sails.sid"]) { + t.Fatal() + } + + // make request headers + if !assert.Equal(t, "HttpRunnerPlus", tCase.TestSteps[0].Request.Headers["User-Agent"]) { + t.Fatal() + } + if !assert.Equal(t, "postman-echo.com", tCase.TestSteps[0].Request.Headers["Host"]) { + t.Fatal() + } + + // make request data + if !assert.Equal(t, nil, tCase.TestSteps[0].Request.Body) { + t.Fatal() + } + if !assert.Equal(t, map[string]interface{}{"foo1": "HDnY8", "foo2": 12.3}, tCase.TestSteps[1].Request.Body) { + t.Fatal() + } + if !assert.Equal(t, "foo1=HDnY8&foo2=12.3", tCase.TestSteps[2].Request.Body) { + t.Fatal() + } + + // make validators + validator, ok := tCase.TestSteps[0].Validators[0].(hrp.Validator) + if !ok || !assert.Equal(t, "status_code", validator.Check) { + t.Fatal() + } + validator, ok = tCase.TestSteps[0].Validators[1].(hrp.Validator) + if !ok || !assert.Equal(t, "headers.\"Content-Type\"", validator.Check) { + t.Fatal() + } + validator, ok = tCase.TestSteps[0].Validators[2].(hrp.Validator) + if !ok || !assert.Equal(t, "body.url", validator.Check) { + t.Fatal() + } +} + +func TestMakeRequestURL(t *testing.T) { + entry := &Entry{ + Request: Request{ + URL: "http://127.0.0.1:8080/api/login", + }, + } + step, err := converterHAR.prepareTestStep(entry) + if !assert.NoError(t, err) { + t.Fatal() + } + + if !assert.Equal(t, "http://127.0.0.1:8080/api/login", step.Request.URL) { + t.Fatal() + } +} + +func TestMakeRequestHeaders(t *testing.T) { + entry := &Entry{ + Request: Request{ + Method: "POST", + Headers: []NVP{ + {Name: "Content-Type", Value: "application/json; charset=utf-8"}, + }, + }, + } + step, err := converterHAR.prepareTestStep(entry) + if !assert.NoError(t, err) { + t.Fatal() + } + + if !assert.Equal(t, map[string]string{ + "Content-Type": "application/json; charset=utf-8", + }, step.Request.Headers) { + t.Fatal() + } +} + +func TestMakeRequestHeadersWithProfileOverride(t *testing.T) { + tCaseConverter := NewTCaseConverter(harPath) + tCaseConverter.SetProfile(harProfileOverridePath) + h := NewConverterHAR(tCaseConverter) + entry := &Entry{ + Request: Request{ + Method: "POST", + Headers: []NVP{ + {Name: "Content-Type", Value: "application/json; charset=utf-8"}, + }, + }, + } + step, err := h.prepareTestStep(entry) + if !assert.NoError(t, err) { + t.Fatal() + } + + if !assert.Equal(t, map[string]string{ + "Content-Type": "application/x-www-form-urlencoded", + }, step.Request.Headers) { + t.Fatal() + } +} + +func TestMakeRequestCookies(t *testing.T) { + entry := &Entry{ + Request: Request{ + Method: "POST", + Cookies: []Cookie{ + {Name: "abc", Value: "123"}, + {Name: "UserName", Value: "leolee"}, + }, + }, + } + step, err := converterHAR.prepareTestStep(entry) + if !assert.NoError(t, err) { + t.Fatal() + } + + if !assert.Equal(t, map[string]string{ + "abc": "123", + "UserName": "leolee", + }, step.Request.Cookies) { + t.Fatal() + } +} + +func TestMakeRequestCookiesWithProfileOverride(t *testing.T) { + tCaseConverter := NewTCaseConverter(harPath) + tCaseConverter.SetProfile(harProfileOverridePath) + h := NewConverterHAR(tCaseConverter) + entry := &Entry{ + Request: Request{ + Method: "POST", + Cookies: []Cookie{ + {Name: "abc", Value: "123"}, + {Name: "UserName", Value: "leolee"}, + }, + }, + } + step, err := h.prepareTestStep(entry) + if !assert.NoError(t, err) { + t.Fatal() + } + + if !assert.Equal(t, map[string]string{ + "UserName": "debugtalk", + }, step.Request.Cookies) { + t.Fatal() + } +} + +func TestMakeRequestDataParams(t *testing.T) { + entry := &Entry{ + Request: Request{ + Method: "POST", + PostData: PostData{ + MimeType: "application/x-www-form-urlencoded; charset=utf-8", + Params: []PostParam{ + {Name: "a", Value: "1"}, + {Name: "b", Value: "2"}, + }, + }, + }, + } + step, err := converterHAR.prepareTestStep(entry) + if !assert.NoError(t, err) { + t.Fatal() + } + + if !assert.Equal(t, "a=1&b=2", step.Request.Body) { + t.Fatal() + } +} + +func TestMakeRequestDataJSON(t *testing.T) { + entry := &Entry{ + Request: Request{ + Method: "POST", + PostData: PostData{ + MimeType: "application/json; charset=utf-8", + Text: "{\"a\":\"1\",\"b\":\"2\"}", + }, + }, + } + step, err := converterHAR.prepareTestStep(entry) + if !assert.NoError(t, err) { + t.Fatal() + } + + if !assert.Equal(t, map[string]interface{}{"a": "1", "b": "2"}, step.Request.Body) { + t.Fatal() + } +} + +func TestMakeRequestDataTextEmpty(t *testing.T) { + entry := &Entry{ + Request: Request{ + Method: "POST", + PostData: PostData{ + MimeType: "application/json; charset=utf-8", + Text: "", + }, + }, + } + step, err := converterHAR.prepareTestStep(entry) + if !assert.NoError(t, err) { + t.Fatal() + } + + if !assert.Equal(t, nil, step.Request.Body) { // TODO + t.Fatal() + } +} + +func TestMakeValidate(t *testing.T) { + entry := &Entry{ + Response: Response{ + Status: 200, + Headers: []NVP{ + {Name: "Content-Type", Value: "application/json; charset=utf-8"}, + }, + Content: Content{ + Size: 71, + MimeType: "application/json; charset=utf-8", + // map[Code:200 IsSuccess:true Message:<nil> Value:map[BlnResult:true]] + Text: "eyJJc1N1Y2Nlc3MiOnRydWUsIkNvZGUiOjIwMCwiTWVzc2FnZSI6bnVsbCwiVmFsdWUiOnsiQmxuUmVzdWx0Ijp0cnVlfX0=", + Encoding: "base64", + }, + }, + } + step, err := converterHAR.prepareTestStep(entry) + if !assert.NoError(t, err) { + t.Fatal() + } + validator, ok := step.Validators[0].(hrp.Validator) + if !ok { + t.Fatal() + } + if !assert.Equal(t, validator, + hrp.Validator{ + Check: "status_code", + Expect: 200, + Assert: "equals", + Message: "assert response status code", + }) { + t.Fatal() + } + + validator, ok = step.Validators[1].(hrp.Validator) + if !ok { + t.Fatal() + } + if !assert.Equal(t, validator, + hrp.Validator{ + Check: "headers.\"Content-Type\"", + Expect: "application/json; charset=utf-8", + Assert: "equals", + Message: "assert response header Content-Type", + }) { + t.Fatal() + } + + validator, ok = step.Validators[2].(hrp.Validator) + if !ok { + t.Fatal() + } + if !assert.Equal(t, validator, + hrp.Validator{ + Check: "body.Code", + Expect: float64(200), // TODO + Assert: "equals", + Message: "assert response body Code", + }) { + t.Fatal() + } +} diff --git a/hrp/internal/convert/converter_json.go b/hrp/internal/convert/converter_json.go new file mode 100644 index 00000000..5aa0b69a --- /dev/null +++ b/hrp/internal/convert/converter_json.go @@ -0,0 +1,111 @@ +package convert + +import ( + "fmt" + + "github.com/pkg/errors" + + "github.com/httprunner/httprunner/v4/hrp" + "github.com/httprunner/httprunner/v4/hrp/internal/builtin" + "github.com/httprunner/httprunner/v4/hrp/internal/version" +) + +func NewConverterJSON(converter *TCaseConverter) *ConverterJSON { + return &ConverterJSON{ + converter: converter, + } +} + +type ConverterJSON struct { + converter *TCaseConverter +} + +func (c *ConverterJSON) Struct() *TCaseConverter { + return c.converter +} + +func (c *ConverterJSON) ToJSON() (string, error) { + testCase, err := c.makeTestCase() + if err != nil { + return "", err + } + jsonPath := c.converter.genOutputPath(suffixJSON) + err = builtin.Dump2JSON(testCase, jsonPath) + if err != nil { + return "", err + } + return jsonPath, nil +} + +func (c *ConverterJSON) ToJSONTemp() (string, error) { + testCase, err := c.makeTestCaseTemp() + if err != nil { + return "", err + } + jsonPath := c.converter.genOutputPath(suffixJSON) + err = builtin.Dump2JSON(testCase, jsonPath) + if err != nil { + return "", err + } + return jsonPath, nil +} + +func (c *ConverterJSON) ToYAML() (string, error) { + testCase, err := c.makeTestCase() + if err != nil { + return "", err + } + yamlPath := c.converter.genOutputPath(suffixYAML) + err = builtin.Dump2YAML(testCase, yamlPath) + if err != nil { + return "", err + } + return yamlPath, nil +} + +func (c *ConverterJSON) ToGoTest() (string, error) { + //TODO implement me + return "", errors.New("convert from json testcase to gotest scripts is not supported yet") +} + +func (c *ConverterJSON) ToPyTest() (string, error) { + return convertToPyTest(c) +} + +func (c *ConverterJSON) MakePyTestScript() (string, error) { + httprunner := fmt.Sprintf("httprunner>=%s", version.HttpRunnerMinVersion) + python3, err := builtin.EnsurePython3Venv(httprunner) + if err != nil { + return "", err + } + args := append([]string{"-m", "httprunner", "make"}, c.converter.InputPath) + err = builtin.ExecCommand(python3, args...) + if err != nil { + return "", err + } + return c.converter.genOutputPath(suffixPyTest), nil +} + +func (c *ConverterJSON) makeTestCase() (*hrp.TCase, error) { + tCase, err := makeTestCaseFromJSONYAML(c) + if err != nil { + return nil, err + } + err = tCase.MakeCompat2GoEngine() + if err != nil { + return nil, err + } + return tCase, nil +} + +func (c *ConverterJSON) makeTestCaseTemp() (*hrp.TCase, error) { + tCase, err := makeTestCaseFromJSONYAML(c) + if err != nil { + return nil, err + } + err = tCase.MakeCompat2PyEngine() + if err != nil { + return nil, err + } + return tCase, nil +} diff --git a/hrp/internal/convert/postman2case/core.go b/hrp/internal/convert/converter_postman.go similarity index 52% rename from hrp/internal/convert/postman2case/core.go rename to hrp/internal/convert/converter_postman.go index 1f15cbf5..d373c23a 100644 --- a/hrp/internal/convert/postman2case/core.go +++ b/hrp/internal/convert/converter_postman.go @@ -1,4 +1,4 @@ -package postman2case +package convert import ( "bytes" @@ -19,6 +19,83 @@ import ( "github.com/httprunner/httprunner/v4/hrp/internal/json" ) +// ==================== model definition starts here ==================== + +/* +Postman Collection format reference: +https://schema.postman.com/json/collection/v2.0.0/collection.json +https://schema.postman.com/json/collection/v2.1.0/collection.json +*/ + +// CasePostman represents the postman exported file +type CasePostman struct { + Info TInfo `json:"info"` + Items []TItem `json:"item"` +} + +// TInfo gives information about the collection +type TInfo struct { + Name string `json:"name"` + Description string `json:"description"` + Schema string `json:"schema"` +} + +// TItem contains the detail information of request and expected responses +// item could be defined recursively +type TItem struct { + Items []TItem `json:"item"` + Name string `json:"name"` + Request TRequest `json:"request"` + Responses []TResponse `json:"response"` +} + +type TRequest struct { + Method string `json:"method"` + Headers []TField `json:"header"` + Body TBody `json:"body"` + URL TUrl `json:"url"` + Description string `json:"description"` +} + +type TResponse struct { + Name string `json:"name"` + OriginalRequest TRequest `json:"originalRequest"` + Status string `json:"status"` + Code int `json:"code"` + Headers []TField `json:"headers"` + Body string `json:"body"` +} + +type TUrl struct { + Raw string `json:"raw"` + Protocol string `json:"protocol"` + Path []string `json:"path"` + Description string `json:"description"` + Query []TField `json:"query"` + Variable []TField `json:"variable"` +} + +type TField struct { + Key string `json:"key"` + Value string `json:"value"` + Src string `json:"src"` + Description string `json:"description"` + Type string `json:"type"` + Disabled bool `json:"disabled"` + Enable bool `json:"enable"` +} + +type TBody struct { + Mode string `json:"mode"` + FormData []TField `json:"formdata"` + URLEncoded []TField `json:"urlencoded"` + Raw string `json:"raw"` + Disabled bool `json:"disabled"` + Options interface{} `json:"options"` +} + +// ==================== model definition ends here ==================== + const ( enumBodyRaw = "raw" enumBodyUrlEncoded = "urlencoded" @@ -32,19 +109,6 @@ const ( enumFieldTypeFile = "file" ) -const ( - suffixName = ".converted" // distinguish the converted json(testcase) from the origin json(collection) - extensionJSON = ".json" - extensionYAML = ".yaml" -) - -const ( - configProfile = "profile" - configPatch = "patch" - keyHeaders = "headers" - keyCookies = "cookies" -) - var contentTypeMap = map[string]string{ "text": "text/plain", "javascript": "application/javascript", @@ -53,119 +117,131 @@ var contentTypeMap = map[string]string{ "xml": "application/xml", } -func NewCollection(path string) *collection { - return &collection{ - path: path, +func NewConverterPostman(converter *TCaseConverter) *ConverterPostman { + return &ConverterPostman{ + converter: converter, } } -type collection struct { - path string - profile map[string]interface{} - patch map[string]interface{} - outputDir string +type ConverterPostman struct { + converter *TCaseConverter } -func (c *collection) SetProfile(path string) { - log.Info().Str("path", path).Msg("set profile") - c.profile = make(map[string]interface{}) - err := builtin.LoadFile(path, c.profile) - if err != nil { - log.Warn().Str("path", path). - Msg("invalid profile format, ignore!") - } +func (c *ConverterPostman) Struct() *TCaseConverter { + return c.converter } -func (c *collection) SetPatch(path string) { - log.Info().Str("path", path).Msg("set patch") - c.patch = make(map[string]interface{}) - err := builtin.LoadFile(path, c.patch) - if err != nil { - log.Warn().Str("path", path). - Msg("invalid patch format, ignore!") - } -} - -func (c *collection) SetOutputDir(dir string) { - log.Info().Str("dir", dir).Msg("set output directory") - c.outputDir = dir -} - -func (c *collection) GenJSON() (jsonPath string, err error) { +func (c *ConverterPostman) ToJSON() (string, error) { testCase, err := c.makeTestCase() if err != nil { return "", err } - jsonPath = c.genOutputPath(extensionJSON) + jsonPath := c.converter.genOutputPath(suffixJSON) err = builtin.Dump2JSON(testCase, jsonPath) - return + if err != nil { + return "", err + } + return jsonPath, nil } -func (c *collection) GenYAML() (yamlPath string, err error) { +func (c *ConverterPostman) ToJSONTemp() (string, error) { + testCase, err := c.makeTestCaseTemp() + if err != nil { + return "", err + } + jsonPath := c.converter.genOutputPath(suffixJSON) + err = builtin.Dump2JSON(testCase, jsonPath) + if err != nil { + return "", err + } + return jsonPath, nil +} + +func (c *ConverterPostman) ToYAML() (string, error) { testCase, err := c.makeTestCase() if err != nil { return "", err } - yamlPath = c.genOutputPath(extensionYAML) + yamlPath := c.converter.genOutputPath(suffixYAML) err = builtin.Dump2YAML(testCase, yamlPath) - return -} - -func (c *collection) genOutputPath(suffix string) string { - file := getFilenameWithoutExtension(c.path) + suffix - if c.outputDir != "" { - return filepath.Join(c.outputDir, file) - } else { - return filepath.Join(filepath.Dir(c.path), file) + if err != nil { + return "", err } + return yamlPath, nil } -func getFilenameWithoutExtension(path string) string { - base := filepath.Base(path) - ext := filepath.Ext(base) - return base[0:len(base)-len(ext)] + suffixName +func (c *ConverterPostman) ToGoTest() (string, error) { + //TODO implement me + return "", errors.New("convert from postman to gotest scripts is not supported yet") } -func (c *collection) makeTestCase() (*hrp.TCase, error) { - tCollection, err := c.load() +func (c *ConverterPostman) ToPyTest() (string, error) { + return convertToPyTest(c) +} + +func (c *ConverterPostman) makeTestCase() (*hrp.TCase, error) { + casePostman, err := c.load() if err != nil { return nil, err } - teststeps, err := c.prepareTestSteps(tCollection) + teststeps, err := c.prepareTestSteps(casePostman) if err != nil { return nil, err } tCase := &hrp.TCase{ - Config: c.prepareConfig(tCollection), + Config: c.prepareConfig(casePostman), TestSteps: teststeps, } + err = tCase.MakeCompat2GoEngine() + if err != nil { + return nil, err + } return tCase, nil } -func (c *collection) load() (*TCollection, error) { - collection := &TCollection{} - err := builtin.LoadFile(c.path, collection) +func (c *ConverterPostman) makeTestCaseTemp() (*hrp.TCase, error) { + casePostman, err := c.load() if err != nil { - return nil, errors.Wrap(err, "load postman collection failed") + return nil, err } - return collection, nil + teststeps, err := c.prepareTestSteps(casePostman) + if err != nil { + return nil, err + } + tCase := &hrp.TCase{ + Config: c.prepareConfig(casePostman), + TestSteps: teststeps, + } + err = tCase.MakeCompat2PyEngine() + if err != nil { + return nil, err + } + return tCase, nil } -func (c *collection) prepareConfig(tCollection *TCollection) *hrp.TConfig { - return hrp.NewConfig(tCollection.Info.Name). +func (c *ConverterPostman) load() (*CasePostman, error) { + casePostman := c.converter.CasePostman + if casePostman == nil { + return nil, errors.New("empty postman case occurs") + } + return casePostman, nil +} + +func (c *ConverterPostman) prepareConfig(casePostman *CasePostman) *hrp.TConfig { + return hrp.NewConfig(casePostman.Info.Name). SetVerifySSL(false) } -func (c *collection) prepareTestSteps(tCollection *TCollection) ([]*hrp.TStep, error) { +func (c *ConverterPostman) prepareTestSteps(casePostman *CasePostman) ([]*hrp.TStep, error) { // recursively convert collection items into a list var itemList []TItem - for _, item := range tCollection.Items { + for _, item := range casePostman.Items { extractItemList(item, &itemList) } var steps []*hrp.TStep for _, item := range itemList { - step, err := c.prepareTestStep(&item) + step, err := c.prepareTestStep(&item, steps) if err != nil { return nil, err } @@ -191,19 +267,18 @@ func extractItemList(item TItem, itemList *[]TItem) { } } -func (c *collection) prepareTestStep(item *TItem) (*hrp.TStep, error) { +func (c *ConverterPostman) prepareTestStep(item *TItem, steps []*hrp.TStep) (*hrp.TStep, error) { log.Info(). Str("method", item.Request.Method). Str("url", item.Request.URL.Raw). Msg("convert teststep") - step := &tStep{ + step := &stepFromPostman{ TStep: hrp.TStep{ Request: &hrp.Request{}, Validators: make([]interface{}, 0), }, - profile: c.profile, - patch: c.patch, + profile: c.converter.Profile, } if err := step.makeRequestName(item); err != nil { return nil, err @@ -223,30 +298,29 @@ func (c *collection) prepareTestStep(item *TItem) (*hrp.TStep, error) { if err := step.makeRequestCookies(item); err != nil { return nil, err } - if err := step.makeRequestBody(item); err != nil { + if err := step.makeRequestBody(item, steps); err != nil { return nil, err } return &step.TStep, nil } -type tStep struct { +type stepFromPostman struct { hrp.TStep - profile map[string]interface{} - patch map[string]interface{} + profile *Profile } // makeRequestName indicates the step name the same as item name -func (s *tStep) makeRequestName(item *TItem) error { +func (s *stepFromPostman) makeRequestName(item *TItem) error { s.Name = item.Name return nil } -func (s *tStep) makeRequestMethod(item *TItem) error { +func (s *stepFromPostman) makeRequestMethod(item *TItem) error { s.Request.Method = hrp.HTTPMethod(item.Request.Method) return nil } -func (s *tStep) makeRequestURL(item *TItem) error { +func (s *stepFromPostman) makeRequestURL(item *TItem) error { rawUrl := item.Request.URL.Raw // parse path variables like ":path" in https://postman-echo.com/:path?k1=v1&k2=v2 for _, field := range item.Request.URL.Variable { @@ -261,7 +335,7 @@ func (s *tStep) makeRequestURL(item *TItem) error { return nil } -func (s *tStep) makeRequestParams(item *TItem) error { +func (s *stepFromPostman) makeRequestParams(item *TItem) error { s.Request.Params = make(map[string]interface{}) for _, field := range item.Request.URL.Query { if field.Disabled { @@ -272,44 +346,9 @@ func (s *tStep) makeRequestParams(item *TItem) error { return nil } -func (s *tStep) updateRequestInfo(config string, key string) bool { - var m map[string]interface{} - switch config { - case configProfile: - m = s.profile - case configPatch: - m = s.patch - default: - return false - } - iRequestMap, existed := m[key] - if existed { - requestMap, ok := iRequestMap.(map[string]interface{}) - if ok { - for k, v := range requestMap { - switch key { - case keyHeaders: - s.Request.Headers[k] = fmt.Sprintf("%v", v) - case keyCookies: - s.Request.Cookies[k] = fmt.Sprintf("%v", v) - } - } - return true - } - log.Warn().Interface(key, iRequestMap).Msgf("%v from %v is not a map, ignore!", key, config) - } - return false -} - -func (s *tStep) makeRequestHeaders(item *TItem) error { - s.Request.Headers = make(map[string]string) - - // override all headers according to the profile - if s.updateRequestInfo(configProfile, keyHeaders) { - return nil - } - +func (s *stepFromPostman) makeRequestHeaders(item *TItem) error { // headers defined in postman collection + s.Request.Headers = make(map[string]string) for _, field := range item.Request.Headers { if field.Disabled || strings.EqualFold(field.Key, "cookie") { continue @@ -317,20 +356,23 @@ func (s *tStep) makeRequestHeaders(item *TItem) error { s.Request.Headers[field.Key] = field.Value } - // create or update the headers indicated in the patch - s.updateRequestInfo(configPatch, keyHeaders) + if s.profile == nil { + return nil + } + // override all headers according to the profile + if s.profile.Override { + s.Request.Headers = make(map[string]string) + } + // create or update the headers according to the profile + for k, v := range s.profile.Headers { + s.Request.Headers[k] = v + } return nil } -func (s *tStep) makeRequestCookies(item *TItem) error { - s.Request.Cookies = make(map[string]string) - - // override all cookies according to the profile - if s.updateRequestInfo(configProfile, keyCookies) { - return nil - } - +func (s *stepFromPostman) makeRequestCookies(item *TItem) error { // cookies defined in postman collection + s.Request.Cookies = make(map[string]string) for _, field := range item.Request.Headers { if field.Disabled || !strings.EqualFold(field.Key, "cookie") { continue @@ -338,12 +380,21 @@ func (s *tStep) makeRequestCookies(item *TItem) error { s.parseRequestCookiesMap(field.Value) } - // create or update the cookies indicated in the patch - s.updateRequestInfo(configPatch, keyCookies) + if s.profile == nil { + return nil + } + // override all cookies according to the profile + if s.profile.Override { + s.Request.Cookies = make(map[string]string) + } + // create or update the cookies according to the profile + for k, v := range s.profile.Cookies { + s.Request.Cookies[k] = v + } return nil } -func (s *tStep) parseRequestCookiesMap(cookies string) { +func (s *stepFromPostman) parseRequestCookiesMap(cookies string) { for _, cookie := range strings.Split(cookies, ";") { cookie = strings.TrimSpace(cookie) index := strings.Index(cookie, "=") @@ -351,11 +402,11 @@ func (s *tStep) parseRequestCookiesMap(cookies string) { log.Warn().Str("cookie", cookie).Msg("cookie format invalid") continue } - s.Request.Cookies[cookie[0:index]] = cookie[index+1:] + s.Request.Cookies[cookie[:index]] = cookie[index+1:] } } -func (s *tStep) makeRequestBody(item *TItem) error { +func (s *stepFromPostman) makeRequestBody(item *TItem, steps []*hrp.TStep) error { mode := item.Request.Body.Mode if mode == "" { return nil @@ -364,7 +415,7 @@ func (s *tStep) makeRequestBody(item *TItem) error { case enumBodyRaw: return s.makeRequestBodyRaw(item) case enumBodyFormData: - return s.makeRequestBodyFormData(item) + return s.makeRequestBodyFormData(item, steps) case enumBodyUrlEncoded: return s.makeRequestBodyUrlEncoded(item) case enumBodyFile, enumBodyGraphQL: @@ -373,7 +424,7 @@ func (s *tStep) makeRequestBody(item *TItem) error { return nil } -func (s *tStep) makeRequestBodyRaw(item *TItem) (err error) { +func (s *stepFromPostman) makeRequestBodyRaw(item *TItem) (err error) { defer func() { if p := recover(); p != nil { err = fmt.Errorf("make request body raw failed: %v", p) @@ -401,7 +452,7 @@ func (s *tStep) makeRequestBodyRaw(item *TItem) (err error) { return } -func (s *tStep) makeRequestBodyFormData(item *TItem) (err error) { +func (s *stepFromPostman) makeRequestBodyFormData(item *TItem, steps []*hrp.TStep) (err error) { defer func() { if err != nil { err = errors.Wrap(err, "make request body form-data failed") @@ -446,7 +497,7 @@ func writeFormDataFile(writer *multipart.Writer, field *TField) error { return err } -func (s *tStep) makeRequestBodyUrlEncoded(item *TItem) error { +func (s *stepFromPostman) makeRequestBodyUrlEncoded(item *TItem) error { payloadMap := make(map[string]string) for _, field := range item.Request.Body.URLEncoded { if field.Disabled { @@ -460,6 +511,6 @@ func (s *tStep) makeRequestBodyUrlEncoded(item *TItem) error { } // TODO makeValidate from example response -func (s *tStep) makeValidate(item *TItem) error { +func (s *stepFromPostman) makeValidate(item *TItem) error { return nil } diff --git a/hrp/internal/convert/postman2case/core_test.go b/hrp/internal/convert/converter_postman_test.go similarity index 73% rename from hrp/internal/convert/postman2case/core_test.go rename to hrp/internal/convert/converter_postman_test.go index a102e136..72994794 100644 --- a/hrp/internal/convert/postman2case/core_test.go +++ b/hrp/internal/convert/converter_postman_test.go @@ -1,4 +1,4 @@ -package postman2case +package convert import ( "testing" @@ -7,13 +7,15 @@ import ( ) var ( - collectionPath = "../../../../examples/data/postman2case/demo.json" - profilePath = "../../../../examples/data/postman2case/profile.yml" - patchPath = "../../../../examples/data/postman2case/patch.yml" + collectionPath = "../../../examples/data/postman2case/demo.json" + collectionProfileOverridePath = "../../../examples/data/postman2case/profile_override.yml" + collectionProfilePath = "../../../examples/data/postman2case/profile.yml" ) -func TestGenJSON(t *testing.T) { - jsonPath, err := NewCollection(collectionPath).GenJSON() +var converterPostman = NewConverterPostman(NewTCaseConverter(collectionPath)) + +func TestPostman2JSON(t *testing.T) { + jsonPath, err := converterPostman.ToJSON() if !assert.NoError(t, err) { t.Fatal() } @@ -22,8 +24,8 @@ func TestGenJSON(t *testing.T) { } } -func TestGenYAML(t *testing.T) { - yamlPath, err := NewCollection(collectionPath).GenYAML() +func TestPostman2YAML(t *testing.T) { + yamlPath, err := converterPostman.ToYAML() if !assert.NoError(t, err) { t.Fatal() } @@ -33,17 +35,17 @@ func TestGenYAML(t *testing.T) { } func TestLoadCollection(t *testing.T) { - tCollection, err := NewCollection(collectionPath).load() + casePostman, err := converterPostman.load() if !assert.NoError(t, err) { t.Fatal(err) } - if !assert.Equal(t, "postman collection demo", tCollection.Info.Name) { + if !assert.Equal(t, "postman collection demo", casePostman.Info.Name) { t.Fatal() } } -func TestMakeTestCase(t *testing.T) { - tCase, err := NewCollection(collectionPath).makeTestCase() +func TestMakeTestCaseFromCollection(t *testing.T) { + tCase, err := converterPostman.makeTestCase() if !assert.NoError(t, err) { t.Fatal() } @@ -107,9 +109,10 @@ func TestMakeTestCase(t *testing.T) { } } -func TestMakeTestCaseWithProfile(t *testing.T) { - c := NewCollection(collectionPath) - c.SetProfile(profilePath) +func TestMakeTestCaseWithProfileOverride(t *testing.T) { + tCaseConverter := NewTCaseConverter(collectionPath) + tCaseConverter.SetProfile(collectionProfileOverridePath) + c := NewConverterPostman(tCaseConverter) tCase, err := c.makeTestCase() if !assert.NoError(t, err) { t.Fatal() @@ -133,22 +136,23 @@ func TestMakeTestCaseWithProfile(t *testing.T) { } } -func TestMakeTestCaseWithPatch(t *testing.T) { - c := NewCollection(collectionPath) - c.SetPatch(patchPath) +func TestMakeTestCaseWithProfile(t *testing.T) { + tCaseConverter := NewTCaseConverter(collectionPath) + tCaseConverter.SetProfile(collectionProfilePath) + c := NewConverterPostman(tCaseConverter) tCase, err := c.makeTestCase() if !assert.NoError(t, err) { t.Fatal() } - // create cookies Cookie1 indicated in patch + // create cookies Cookie1 indicated in profile if !assert.Equal(t, "this cookie will be created or updated", tCase.TestSteps[0].Request.Cookies["Cookie1"]) { t.Fatal() } - // update header User-Agent indicated in patch + // update header User-Agent indicated in profile if !assert.Equal(t, "this header will be created or updated", tCase.TestSteps[5].Request.Headers["User-Agent"]) { t.Fatal() } - // pass header Connection which is not indicated in patch + // pass header Connection which is not indicated in profile if !assert.Equal(t, "close", tCase.TestSteps[5].Request.Headers["Connection"]) { t.Fatal() } diff --git a/hrp/internal/convert/converter_pytest.go b/hrp/internal/convert/converter_pytest.go new file mode 100644 index 00000000..8c094900 --- /dev/null +++ b/hrp/internal/convert/converter_pytest.go @@ -0,0 +1,19 @@ +package convert + +import ( + "fmt" + + "github.com/httprunner/httprunner/v4/hrp/internal/builtin" + "github.com/httprunner/httprunner/v4/hrp/internal/version" +) + +func convert2PyTestScripts(paths ...string) error { + httprunner := fmt.Sprintf("httprunner>=%s", version.HttpRunnerMinVersion) + python3, err := builtin.EnsurePython3Venv(httprunner) + if err != nil { + return err + } + + args := append([]string{"-m", "httprunner", "make"}, paths...) + return builtin.ExecCommand(python3, args...) +} diff --git a/hrp/internal/convert/converter_yaml.go b/hrp/internal/convert/converter_yaml.go new file mode 100644 index 00000000..81d1b1a9 --- /dev/null +++ b/hrp/internal/convert/converter_yaml.go @@ -0,0 +1,94 @@ +package convert + +import ( + "github.com/pkg/errors" + + "github.com/httprunner/httprunner/v4/hrp" + "github.com/httprunner/httprunner/v4/hrp/internal/builtin" +) + +func NewConverterYAML(converter *TCaseConverter) *ConverterYAML { + return &ConverterYAML{ + converter: converter, + } +} + +type ConverterYAML struct { + converter *TCaseConverter +} + +func (c *ConverterYAML) Struct() *TCaseConverter { + return c.converter +} + +func (c *ConverterYAML) ToJSON() (string, error) { + testCase, err := c.makeTestCase() + if err != nil { + return "", err + } + jsonPath := c.converter.genOutputPath(suffixJSON) + err = builtin.Dump2JSON(testCase, jsonPath) + if err != nil { + return "", err + } + return jsonPath, nil +} + +func (c *ConverterYAML) ToJSONTemp() (string, error) { + testCase, err := c.makeTestCaseTemp() + if err != nil { + return "", err + } + jsonPath := c.converter.genOutputPath(suffixJSON) + err = builtin.Dump2JSON(testCase, jsonPath) + if err != nil { + return "", err + } + return jsonPath, nil +} + +func (c *ConverterYAML) ToYAML() (string, error) { + testCase, err := c.makeTestCase() + if err != nil { + return "", err + } + yamlPath := c.converter.genOutputPath(suffixYAML) + err = builtin.Dump2YAML(testCase, yamlPath) + if err != nil { + return "", err + } + return yamlPath, nil +} + +func (c *ConverterYAML) ToGoTest() (string, error) { + //TODO implement me + return "", errors.New("convert from yaml testcase to gotest scripts is not supported yet") +} + +func (c *ConverterYAML) ToPyTest() (string, error) { + return convertToPyTest(c) +} + +func (c *ConverterYAML) makeTestCase() (*hrp.TCase, error) { + tCase, err := makeTestCaseFromJSONYAML(c) + if err != nil { + return nil, err + } + err = tCase.MakeCompat2GoEngine() + if err != nil { + return nil, err + } + return tCase, nil +} + +func (c *ConverterYAML) makeTestCaseTemp() (*hrp.TCase, error) { + tCase, err := makeTestCaseFromJSONYAML(c) + if err != nil { + return nil, err + } + err = tCase.MakeCompat2PyEngine() + if err != nil { + return nil, err + } + return tCase, nil +} diff --git a/hrp/internal/convert/har2case/core.go b/hrp/internal/convert/har2case/core.go index 0e96a96d..25824855 100644 --- a/hrp/internal/convert/har2case/core.go +++ b/hrp/internal/convert/har2case/core.go @@ -22,13 +22,6 @@ const ( suffixYAML = ".yaml" ) -const ( - configProfile = "profile" - configPatch = "patch" - keyHeaders = "headers" - keyCookies = "cookies" -) - func NewHAR(path string) *har { return &har{ path: path, @@ -40,7 +33,6 @@ type har struct { filterStr string excludeStr string profile map[string]interface{} - patch map[string]interface{} outputDir string } @@ -54,16 +46,6 @@ func (h *har) SetProfile(path string) { } } -func (h *har) SetPatch(path string) { - log.Info().Str("path", path).Msg("set patch") - h.patch = make(map[string]interface{}) - err := builtin.LoadFile(path, h.patch) - if err != nil { - log.Warn().Str("path", path). - Msg("invalid patch format, ignore!") - } -} - func (h *har) SetOutputDir(dir string) { log.Info().Str("dir", dir).Msg("set output directory") h.outputDir = dir @@ -164,7 +146,6 @@ func (h *har) prepareTestStep(entry *Entry) (*hrp.TStep, error) { Validators: make([]interface{}, 0), }, profile: h.profile, - patch: h.patch, } if err := step.makeRequestMethod(entry); err != nil { return nil, err @@ -193,7 +174,6 @@ func (h *har) prepareTestStep(entry *Entry) (*hrp.TStep, error) { type tStep struct { hrp.TStep profile map[string]interface{} - patch map[string]interface{} } func (s *tStep) makeRequestMethod(entry *Entry) error { @@ -219,59 +199,43 @@ func (s *tStep) makeRequestParams(entry *Entry) error { return nil } -func (s *tStep) updateRequestInfo(config string, key string) bool { - var m map[string]interface{} - switch config { - case configProfile: - m = s.profile - case configPatch: - m = s.patch - default: - return false - } - iRequestMap, existed := m[key] - if existed { - requestMap, ok := iRequestMap.(map[string]interface{}) - if ok { - for k, v := range requestMap { - switch key { - case keyHeaders: - s.Request.Headers[k] = fmt.Sprintf("%v", v) - case keyCookies: - s.Request.Cookies[k] = fmt.Sprintf("%v", v) - } - } - return true - } - log.Warn().Interface(key, iRequestMap).Msgf("%v from %v is not a map, ignore!", key, config) - } - return false -} - func (s *tStep) makeRequestCookies(entry *Entry) error { s.Request.Cookies = make(map[string]string) - - // override all cookies according to the profile - if s.updateRequestInfo(configProfile, keyCookies) { - return nil + cookies, ok := s.profile["cookies"] + if ok { + // use cookies from profile + cookies, ok := cookies.(map[string]interface{}) + if ok { + for k, v := range cookies { + s.Request.Cookies[k] = fmt.Sprintf("%v", v) + } + return nil + } + log.Warn().Interface("cookies", cookies). + Msg("cookies from profile is not a map, ignore!") } // use cookies from har for _, cookie := range entry.Request.Cookies { s.Request.Cookies[cookie.Name] = cookie.Value } - - // create or update the cookies indicated in the patch - s.updateRequestInfo(configPatch, keyCookies) return nil } func (s *tStep) makeRequestHeaders(entry *Entry) error { s.Request.Headers = make(map[string]string) - - // override all headers according to the profile - if s.updateRequestInfo(configProfile, keyHeaders) { - return nil + headers, ok := s.profile["headers"] + if ok { + // use headers from profile + cookies, ok := headers.(map[string]interface{}) + if ok { + for k, v := range cookies { + s.Request.Headers[k] = fmt.Sprintf("%v", v) + } + return nil + } + log.Warn().Interface("headers", headers). + Msg("headers from profile is not a map, ignore!") } // use headers from har @@ -281,9 +245,6 @@ func (s *tStep) makeRequestHeaders(entry *Entry) error { } s.Request.Headers[header.Name] = header.Value } - - // create or update the headers indicated in the patch - s.updateRequestInfo(configPatch, keyHeaders) return nil } diff --git a/hrp/internal/convert/har2case/core_test.go b/hrp/internal/convert/har2case/core_test.go index ce6466fe..0fc6a3cb 100644 --- a/hrp/internal/convert/har2case/core_test.go +++ b/hrp/internal/convert/har2case/core_test.go @@ -1,7 +1,6 @@ package har2case import ( - "fmt" "testing" "github.com/stretchr/testify/assert" @@ -12,7 +11,7 @@ import ( var ( harPath = "../../../../examples/data/har/demo.har" harPath2 = "../../../../examples/data/har/postman-echo.har" - profilePath = "../../../../examples/data/har/profile.yml" + profilePath = "../../../../examples/data/har/profile_override.yml" ) func TestGenJSON(t *testing.T) { @@ -382,32 +381,3 @@ func TestMakeValidate(t *testing.T) { t.Fatal() } } - -func Test_tStep_makeRequestCookies(t *testing.T) { - type fields struct { - TStep hrp.TStep - profile map[string]interface{} - patch map[string]interface{} - } - type args struct { - entry *Entry - } - tests := []struct { - name string - fields fields - args args - wantErr assert.ErrorAssertionFunc - }{ - // TODO: Add test cases. - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - s := &tStep{ - TStep: tt.fields.TStep, - profile: tt.fields.profile, - patch: tt.fields.patch, - } - tt.wantErr(t, s.makeRequestCookies(tt.args.entry), fmt.Sprintf("makeRequestCookies(%v)", tt.args.entry)) - }) - } -} diff --git a/hrp/internal/convert/postman2case/collection.go b/hrp/internal/convert/postman2case/collection.go deleted file mode 100644 index ddabee21..00000000 --- a/hrp/internal/convert/postman2case/collection.go +++ /dev/null @@ -1,74 +0,0 @@ -package postman2case - -/* -Postman Collection format reference: -https://schema.postman.com/json/collection/v2.0.0/collection.json -https://schema.postman.com/json/collection/v2.1.0/collection.json -*/ - -// TCollection represents the postman exported file -type TCollection struct { - Info TInfo `json:"info"` - Items []TItem `json:"item"` -} - -// TInfo gives information about the collection -type TInfo struct { - Name string `json:"name"` - Description string `json:"description"` - Schema string `json:"schema"` -} - -// TItem contains the detail information of request and expected responses -// item could be defined recursively -type TItem struct { - Items []TItem `json:"item"` - Name string `json:"name"` - Request TRequest `json:"request"` - Responses []TResponse `json:"response"` -} - -type TRequest struct { - Method string `json:"method"` - Headers []TField `json:"header"` - Body TBody `json:"body"` - URL TUrl `json:"url"` - Description string `json:"description"` -} - -type TResponse struct { - Name string `json:"name"` - OriginalRequest TRequest `json:"originalRequest"` - Status string `json:"status"` - Code int `json:"code"` - Headers []TField `json:"headers"` - Body string `json:"body"` -} - -type TUrl struct { - Raw string `json:"raw"` - Protocol string `json:"protocol"` - Path []string `json:"path"` - Description string `json:"description"` - Query []TField `json:"query"` - Variable []TField `json:"variable"` -} - -type TField struct { - Key string `json:"key"` - Value string `json:"value"` - Src string `json:"src"` - Description string `json:"description"` - Type string `json:"type"` - Disabled bool `json:"disabled"` - Enable bool `json:"enable"` -} - -type TBody struct { - Mode string `json:"mode"` - FormData []TField `json:"formdata"` - URLEncoded []TField `json:"urlencoded"` - Raw string `json:"raw"` - Disabled bool `json:"disabled"` - Options interface{} `json:"options"` -} diff --git a/hrp/internal/convert/case2script/testcase.tmpl b/hrp/internal/convert/testcase.tmpl similarity index 100% rename from hrp/internal/convert/case2script/testcase.tmpl rename to hrp/internal/convert/testcase.tmpl diff --git a/hrp/step_api.go b/hrp/step_api.go index 1c9992ba..7b57d896 100644 --- a/hrp/step_api.go +++ b/hrp/step_api.go @@ -47,7 +47,7 @@ func (path *APIPath) ToAPI() (*API, error) { if err != nil { return nil, err } - err = convertCompatValidator(api.Validators) + err = convertValidatorCompat2GoEngine(api.Validators) convertExtract(api.Extract) return api, err } diff --git a/hrp/testcase.go b/hrp/testcase.go index 05c2b051..d2c2bd80 100644 --- a/hrp/testcase.go +++ b/hrp/testcase.go @@ -64,7 +64,7 @@ func (path *TestCasePath) ToTestCase() (*TestCase, error) { return nil, errors.New("incorrect testcase file format, expected config in file") } - err = tc.makeCompat() + err = tc.MakeCompat2GoEngine() if err != nil { return nil, err } @@ -154,27 +154,28 @@ type TCase struct { TestSteps []*TStep `json:"teststeps" yaml:"teststeps"` } -// makeCompat converts TCase to compatible testcase -func (tc *TCase) makeCompat() error { - var err error +// MakeCompat2GoEngine converts TCase compatible with Golang engine style +func (tc *TCase) MakeCompat2GoEngine() (err error) { defer func() { if p := recover(); p != nil { - err = fmt.Errorf("convert compat testcase error: %v", p) + err = fmt.Errorf("[MakeCompat2GoEngine] convert compat testcase error: %v", p) } }() for _, step := range tc.TestSteps { - // 1. deal with request body compatible with HttpRunner + // 1. deal with request body compatibility if step.Request != nil && step.Request.Body == nil { if step.Request.Json != nil { step.Request.Headers["Content-Type"] = "application/json; charset=utf-8" step.Request.Body = step.Request.Json + step.Request.Json = nil } else if step.Request.Data != nil { step.Request.Body = step.Request.Data + step.Request.Data = nil } } - // 2. deal with validators compatible with HttpRunner - err = convertCompatValidator(step.Validators) + // 2. deal with validators compatibility + err = convertValidatorCompat2GoEngine(step.Validators) if err != nil { return err } @@ -185,16 +186,19 @@ func (tc *TCase) makeCompat() error { return nil } -func convertCompatValidator(Validators []interface{}) (err error) { +func convertValidatorCompat2GoEngine(Validators []interface{}) (err error) { for i, iValidator := range Validators { + if _, ok := iValidator.(Validator); ok { + continue + } validatorMap := iValidator.(map[string]interface{}) validator := Validator{} _, checkExisted := validatorMap["check"] _, assertExisted := validatorMap["assert"] _, expectExisted := validatorMap["expect"] - // check priority: HRP > HttpRunner + // validator check priority: Golang > Python engine style if checkExisted && assertExisted && expectExisted { - // HRP validator format + // Golang engine style validator.Check = validatorMap["check"].(string) validator.Assert = validatorMap["assert"].(string) validator.Expect = validatorMap["expect"] @@ -203,8 +207,10 @@ func convertCompatValidator(Validators []interface{}) (err error) { } validator.Check = convertCheckExpr(validator.Check) Validators[i] = validator - } else if len(validatorMap) == 1 { - // HttpRunner validator format + continue + } + if len(validatorMap) == 1 { + // Python engine style for assertMethod, iValidatorContent := range validatorMap { checkAndExpect := iValidatorContent.([]interface{}) if len(checkAndExpect) != 2 { @@ -216,9 +222,9 @@ func convertCompatValidator(Validators []interface{}) (err error) { } validator.Check = convertCheckExpr(validator.Check) Validators[i] = validator - } else { - return fmt.Errorf("unexpected validator format: %v", validatorMap) + continue } + return fmt.Errorf("unexpected validator format: %v", validatorMap) } return nil } @@ -244,6 +250,74 @@ func convertCheckExpr(checkExpr string) string { return strings.Join(checkItems, ".") } +// MakeCompat2PyEngine converts TCase compatible with Python engine style +func (tc *TCase) MakeCompat2PyEngine() (err error) { + defer func() { + if p := recover(); p != nil { + err = fmt.Errorf("[MakeCompat2PyEngine] convert compat testcase error: %v", p) + } + }() + for _, step := range tc.TestSteps { + // 1. deal with request body compatibility + if step.Request != nil && step.Request.Body != nil { + if strings.HasPrefix(step.Request.Headers["Content-Type"], "application/json") { + step.Request.Json = step.Request.Body + step.Request.Body = nil + continue + } + step.Request.Data = step.Request.Body + step.Request.Body = nil + } + + // 2. deal with validators compatibility + err = convertValidatorCompat2PyEngine(step.Validators) + if err != nil { + return err + } + } + return +} + +func convertValidatorCompat2PyEngine(Validators []interface{}) (err error) { + for i, iValidator := range Validators { + if v, ok := iValidator.(Validator); ok { + var iValidatorContent []interface{} + iValidatorContent = append(iValidatorContent, v.Check) + iValidatorContent = append(iValidatorContent, v.Expect) + newValidatorMap := make(map[string]interface{}) + newValidatorMap[v.Assert] = iValidatorContent + Validators[i] = newValidatorMap + continue + } + validatorMap := iValidator.(map[string]interface{}) + // validator check priority: Python > Golang engine style + if len(validatorMap) == 1 { + // Python engine style + for _, iValidatorContent := range validatorMap { + checkAndExpect := iValidatorContent.([]interface{}) + if len(checkAndExpect) != 2 { + return fmt.Errorf("unexpected validator format: %v", validatorMap) + } + } + continue + } + _, checkExisted := validatorMap["check"] + _, assertExisted := validatorMap["assert"] + _, expectExisted := validatorMap["expect"] + if checkExisted && assertExisted && expectExisted { + // Golang engine style + var iValidatorContent []interface{} + iValidatorContent = append(iValidatorContent, validatorMap["check"]) + iValidatorContent = append(iValidatorContent, validatorMap["expect"]) + newValidatorMap := make(map[string]interface{}) + newValidatorMap[validatorMap["assert"].(string)] = iValidatorContent + Validators[i] = newValidatorMap + continue + } + return fmt.Errorf("unexpected validator format: %v", validatorMap) + } + return +} func LoadTestCases(iTestCases ...ITestCase) ([]*TestCase, error) { testCases := make([]*TestCase, 0) From d96baa789b529cfbd0feb5272153af4c2e41cebc Mon Sep 17 00:00:00 2001 From: buyuxiang <347586493@qq.com> Date: Tue, 24 May 2022 20:50:53 +0800 Subject: [PATCH 053/109] fix comment --- docs/cmd/hrp.md | 2 +- docs/cmd/hrp_convert.md | 2 +- .../postman_collection.json} | 0 .../{postman2case => postman}/profile.yml | 0 .../profile_override.yml | 0 hrp/cmd/convert.go | 5 +- hrp/cmd/har2case.go | 50 +-- hrp/internal/builtin/utils.go | 2 +- hrp/internal/convert/README.md | 28 +- hrp/internal/convert/asset/flowgram.png | Bin 0 -> 59725 bytes hrp/internal/convert/asset/flowgram.svg | 1 - hrp/internal/convert/converter.go | 23 +- hrp/internal/convert/converter_har.go | 2 +- .../convert/converter_postman_test.go | 6 +- hrp/internal/convert/har2case/README.md | 9 - hrp/internal/convert/har2case/core.go | 385 ------------------ hrp/internal/convert/har2case/core_test.go | 383 ----------------- hrp/internal/convert/har2case/har.go | 340 ---------------- hrp/testcase.go | 48 ++- 19 files changed, 82 insertions(+), 1204 deletions(-) rename examples/data/{postman2case/demo.json => postman/postman_collection.json} (100%) rename examples/data/{postman2case => postman}/profile.yml (100%) rename examples/data/{postman2case => postman}/profile_override.yml (100%) create mode 100644 hrp/internal/convert/asset/flowgram.png delete mode 100644 hrp/internal/convert/asset/flowgram.svg delete mode 100644 hrp/internal/convert/har2case/README.md delete mode 100644 hrp/internal/convert/har2case/core.go delete mode 100644 hrp/internal/convert/har2case/core_test.go delete mode 100644 hrp/internal/convert/har2case/har.go diff --git a/docs/cmd/hrp.md b/docs/cmd/hrp.md index 578091e9..9e7fe395 100644 --- a/docs/cmd/hrp.md +++ b/docs/cmd/hrp.md @@ -30,7 +30,7 @@ Copyright 2017 debugtalk ### SEE ALSO * [hrp boom](hrp_boom.md) - run load test with boomer -* [hrp convert](hrp_convert.md) - convert external cases to JSON/YAML/gotest/pytest testcases +* [hrp convert](hrp_convert.md) - convert to JSON/YAML/gotest/pytest testcases * [hrp har2case](hrp_har2case.md) - convert HAR to json/yaml testcase files * [hrp pytest](hrp_pytest.md) - run API test with pytest * [hrp run](hrp_run.md) - run API test with go engine diff --git a/docs/cmd/hrp_convert.md b/docs/cmd/hrp_convert.md index d4771aad..80fcaf2f 100644 --- a/docs/cmd/hrp_convert.md +++ b/docs/cmd/hrp_convert.md @@ -1,6 +1,6 @@ ## hrp convert -convert external cases to JSON/YAML/gotest/pytest testcases +convert to JSON/YAML/gotest/pytest testcases ``` hrp convert $path... [flags] diff --git a/examples/data/postman2case/demo.json b/examples/data/postman/postman_collection.json similarity index 100% rename from examples/data/postman2case/demo.json rename to examples/data/postman/postman_collection.json diff --git a/examples/data/postman2case/profile.yml b/examples/data/postman/profile.yml similarity index 100% rename from examples/data/postman2case/profile.yml rename to examples/data/postman/profile.yml diff --git a/examples/data/postman2case/profile_override.yml b/examples/data/postman/profile_override.yml similarity index 100% rename from examples/data/postman2case/profile_override.yml rename to examples/data/postman/profile_override.yml diff --git a/hrp/cmd/convert.go b/hrp/cmd/convert.go index 31c536e4..a4c8d663 100644 --- a/hrp/cmd/convert.go +++ b/hrp/cmd/convert.go @@ -10,7 +10,7 @@ import ( var convertCmd = &cobra.Command{ Use: "convert $path...", - Short: "convert external cases to JSON/YAML/gotest/pytest testcases", + Short: "convert to JSON/YAML/gotest/pytest testcases", Args: cobra.MinimumNArgs(1), PreRun: func(cmd *cobra.Command, args []string) { setLogLevel(logLevel) @@ -36,8 +36,7 @@ var convertCmd = &cobra.Command{ if flagCount > 1 { return errors.New("please specify at most one conversion flag") } - iCaseConverters := convert.LoadConverters(outputType, outputDir, profilePath, args) - convert.Run(iCaseConverters) + convert.Run(outputType, outputDir, profilePath, args) return nil }, } diff --git a/hrp/cmd/har2case.go b/hrp/cmd/har2case.go index d26fc4ff..9d5f2f10 100644 --- a/hrp/cmd/har2case.go +++ b/hrp/cmd/har2case.go @@ -3,10 +3,9 @@ package cmd import ( "errors" - "github.com/rs/zerolog/log" "github.com/spf13/cobra" - "github.com/httprunner/httprunner/v4/hrp/internal/convert/har2case" + "github.com/httprunner/httprunner/v4/hrp/internal/convert" ) // har2caseCmd represents the har2case command @@ -19,39 +18,20 @@ var har2caseCmd = &cobra.Command{ setLogLevel(logLevel) }, RunE: func(cmd *cobra.Command, args []string) error { - var outputFiles []string - for _, arg := range args { - // must choose one - if !har2caseGenYAMLFlag && !har2caseGenJSONFlag { - return errors.New("please select convert format type") - } - var outputPath string - var err error - - har := har2case.NewHAR(arg) - - // specify output dir - if har2caseOutputDir != "" { - har.SetOutputDir(har2caseOutputDir) - } - - // specify profile - if har2caseProfilePath != "" { - har.SetProfile(har2caseProfilePath) - } - - // generate json/yaml files - if har2caseGenYAMLFlag { - outputPath, err = har.GenYAML() - } else { - outputPath, err = har.GenJSON() // default - } - if err != nil { - return err - } - outputFiles = append(outputFiles, outputPath) + var flagCount int + var har2caseOutputType convert.OutputType + if har2caseGenJSONFlag { + flagCount++ } - log.Info().Strs("output", outputFiles).Msg("convert testcase success") + if har2caseGenYAMLFlag { + flagCount++ + har2caseOutputType = convert.OutputTypeYAML + } + if flagCount > 1 { + return errors.New("please specify at most one conversion flag") + + } + convert.Run(har2caseOutputType, har2caseOutputDir, har2caseProfilePath, args) return nil }, } @@ -65,7 +45,7 @@ var ( func init() { rootCmd.AddCommand(har2caseCmd) - har2caseCmd.Flags().BoolVarP(&har2caseGenJSONFlag, "to-json", "j", true, "convert to JSON format") + har2caseCmd.Flags().BoolVarP(&har2caseGenJSONFlag, "to-json", "j", false, "convert to JSON format (default)") har2caseCmd.Flags().BoolVarP(&har2caseGenYAMLFlag, "to-yaml", "y", false, "convert to YAML format") har2caseCmd.Flags().StringVarP(&har2caseOutputDir, "output-dir", "d", "", "specify output directory, default to the same dir with har file") har2caseCmd.Flags().StringVarP(&har2caseProfilePath, "profile", "p", "", "specify profile path to override headers and cookies") diff --git a/hrp/internal/builtin/utils.go b/hrp/internal/builtin/utils.go index d32adfde..27098249 100644 --- a/hrp/internal/builtin/utils.go +++ b/hrp/internal/builtin/utils.go @@ -286,7 +286,7 @@ func LoadFile(path string, structObj interface{}) (err error) { return errors.Wrap(err, "read file failed") } // remove BOM at the beginning of file - file = bytes.Trim(file, "\xef\xbb\xbf") + file = bytes.TrimLeft(file, "\xef\xbb\xbf") ext := filepath.Ext(path) switch ext { case ".json", ".har": diff --git a/hrp/internal/convert/README.md b/hrp/internal/convert/README.md index 474c8c0e..d31381be 100644 --- a/hrp/internal/convert/README.md +++ b/hrp/internal/convert/README.md @@ -1,9 +1,10 @@ # hrp convert ## 快速上手 + ```shell $ hrp convert -h -convert external cases to JSON/YAML/gotest/pytest testcases +convert to JSON/YAML/gotest/pytest testcases Usage: hrp convert $path... [flags] @@ -21,22 +22,22 @@ Global Flags: --log-json set log to json format -l, --log-level string set log level (default "INFO") ``` + `hrp convert` 指令用于将 HAR/Postman/JMeter/Swagger 等格式的外部脚本转化为 JSON/YAML/gotest/pytest 形态的测试用例,同时也支持测试用例各个形态之间的相互转化,输出的测试用例文件名格式为 `不带扩展名的原文件名称` + `_test` + `json/yaml/go/py` 后缀。 -该指令的所有参数的详细介绍如下: +该指令所有选项的详细说明如下: -1. `--to-json / --to-yaml / --to-gotest / --to-pytest` 用于将输入的外部脚本转化为对应形态的测试用例,四个参数中最多只能指定一个,如果不指定则默认会将输入转化为 JSON 形态的测试用例 +1. `--to-json / --to-yaml / --to-gotest / --to-pytest` 用于将输入的外部脚本转化为对应形态的测试用例,四个选项中最多只能指定一个,如果不指定则默认会将输入转化为 JSON 形态的测试用例 2. `--output-dir` 后接测试用例的期望输出目录的路径,用于将转换生成的测试用例输出到对应的文件夹 -3. `--profile` 后接 `profile` 配置文件的路径,目前支持替换(不存在则会创建)或者覆盖输入的外部脚本/测试用例中的 `Headers` 和 `Cookies` 信息,`profile` 文件的后缀可以为 `json/yaml/yml`,下面给出两类 `profile` 配置文件的示例: -- 根据 `profile` 替换指定的 `Headers` 和 `Cookies` 信息 +3. `--profile` 后接 profile 配置文件的路径,目前支持替换(不存在则会创建)或者覆盖输入的外部脚本/测试用例中的 `Headers` 和 `Cookies` 信息,profile 文件的后缀可以为 `json/yaml/yml`,下面给出两类 profile 配置文件的示例: +- 根据 profile 替换指定的 `Headers` 和 `Cookies` 信息 ```yaml headers: Header1: "this header will be created or updated" cookies: Cookie1: "this cookie will be created or updated" - ``` -- 根据 `profile` 覆盖原有的 `Headers` 和 `Cookies` 信息 +- 根据 profile 覆盖原有的 `Headers` 和 `Cookies` 信息 ```yaml override: true headers: @@ -46,22 +47,29 @@ cookies: ``` ## 注意事项 -1. 指定 `override` 为 `false/true` 可以选择 `profile` 的修改模式为替换/覆盖。需要注意的是,如果不指定该字段则 `profile` 的默认修改模式为**替换**模式, -2. 输入为 JSON/YAML 测试用例时,良好兼容 Golang/Python 双引擎之间的差异(请求体、断言部分的格式略有不同),输出的 JSON/YAML 则统一采用 Golang 引擎的风格 + +1. `hrp convert` 可以自动识别输入类型,因此不需要通过选项来手动制定输入类型,如遇到无法识别、不支持或转换失败的情况,则会输出错误日志并跳过,不会影响其他转换过程的正常进行 +2. 在 profile 文件中,指定 `override` 字段为 `false/true` 可以选择修改模式为替换/覆盖。需要注意的是,如果不指定该字段则 profile 的默认修改模式为替换模式 +3. 输入为 JSON/YAML 测试用例时,良好兼容 Golang/Python 双引擎的请求体、断言格式细微差异,输出的 JSON/YAML 则统一采用 Golang 引擎的风格 ## 转换流程图 -![flow chart](asset/flowgram.svg) +`hrp convert` 的转换过程流程图如下: +![flow chart](asset/flowgram.png) ## 开发进度 +`hrp convert` 当前的开发进度如下: + | from \ to | JSON | YAML | GoTest | PyTest | |:---------:|:----:|:----:|:------:|:------:| | HAR | ✅ | ✅ | ❌ | ✅ | | Postman | ✅ | ✅ | ❌ | ✅ | | JMeter | ❌ | ❌ | ❌ | ❌ | | Swagger | ❌ | ❌ | ❌ | ❌ | +| curl | ❌ | ❌ | ❌ | ❌ | +| Apache ab | ❌ | ❌ | ❌ | ❌ | | JSON | ✅ | ✅ | ❌ | ✅ | | YAML | ✅ | ✅ | ❌ | ✅ | | GoTest | ❌ | ❌ | ❌ | ❌ | diff --git a/hrp/internal/convert/asset/flowgram.png b/hrp/internal/convert/asset/flowgram.png new file mode 100644 index 0000000000000000000000000000000000000000..3e676ec718bca2e95130b5df9f145921d8a76784 GIT binary patch literal 59725 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 literal 0 HcmV?d00001 diff --git a/hrp/internal/convert/asset/flowgram.svg b/hrp/internal/convert/asset/flowgram.svg deleted file mode 100644 index 76652f6b..00000000 --- a/hrp/internal/convert/asset/flowgram.svg +++ /dev/null @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="626px" height="713px" viewBox="-0.5 -0.5 626 713"><defs><linearGradient x1="0%" y1="0%" x2="0%" y2="100%" id="mx-gradient-ffffff-1-ffffff-1-s-2886-0"><stop offset="0%" style="stop-color:#FFFFFF"/><stop offset="100%" style="stop-color:#ffffff"/></linearGradient></defs><g><rect x="72" y="536" width="480" height="160" rx="24" ry="24" fill="#cce5ff" stroke="none" pointer-events="all"/><path d="M 71.5 151 L 71.5 161 L 37 161 L 37 611 L 92.5 611 L 92.5 600.5 L 111.5 616 L 92.5 631.5 L 92.5 621 L 27 621 L 27 151 Z" fill="#ffffff" stroke="#000000" stroke-miterlimit="1.42" pointer-events="all"/><path d="M 92.5 611 L 92.5 600.5 L 111.5 616 L 92.5 631.5 L 92.5 621" fill="none" stroke="#000000" stroke-miterlimit="4" pointer-events="all"/><path d="M 552.5 161 L 552.5 151 L 597 151 L 597 621 L 531.5 621 L 531.5 631.5 L 512.5 616 L 531.5 600.5 L 531.5 611 L 587 611 L 587 161 Z" fill="#ffffff" stroke="#000000" stroke-miterlimit="1.42" pointer-events="all"/><path d="M 531.5 621 L 531.5 631.5 L 512.5 616 L 531.5 600.5 L 531.5 611" fill="none" stroke="#000000" stroke-miterlimit="4" pointer-events="all"/><rect x="72" y="16" width="480" height="280" rx="42" ry="42" fill="#e5ccff" stroke="none" pointer-events="all"/><rect x="72" y="336" width="480" height="160" rx="24" ry="24" fill="#ccffcc" stroke="none" pointer-events="all"/><rect x="112" y="56" width="160" height="80" rx="12" ry="12" fill="url(#mx-gradient-ffffff-1-ffffff-1-s-2886-0)" stroke="#000000" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 158px; height: 1px; padding-top: 96px; margin-left: 113px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 14px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; "><span style="color: rgb(37, 37, 37); font-family: Roboto, arial, sans-serif; text-align: start; font-size: 14px;"><font style="font-size: 14px;">HTTP 存档格式文件<br style="font-size: 14px;" />(.har)<br style="font-size: 14px;" /></font></span></div></div></div></foreignObject></g><rect x="352" y="56" width="160" height="80" rx="12" ry="12" fill="url(#mx-gradient-ffffff-1-ffffff-1-s-2886-0)" stroke="#000000" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 158px; height: 1px; padding-top: 96px; margin-left: 353px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 14px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; "><span style="color: rgb(37, 37, 37); font-family: Roboto, arial, sans-serif; text-align: start; font-size: 14px;"><font style="font-size: 14px;">Postman 项目文件<br style="font-size: 14px;" />(.json)<br style="font-size: 14px;" /></font></span></div></div></div></foreignObject></g><rect x="352" y="176" width="160" height="80" rx="12" ry="12" fill="url(#mx-gradient-ffffff-1-ffffff-1-s-2886-0)" stroke="#000000" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 158px; height: 1px; padding-top: 216px; margin-left: 353px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 14px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; "><span style="color: rgb(37, 37, 37); font-family: Roboto, arial, sans-serif; text-align: start; font-size: 14px;"><font style="font-size: 14px;">JMeter 项目文件<br style="font-size: 14px;" />(.jmx)<br style="font-size: 14px;" /></font></span></div></div></div></foreignObject></g><rect x="112" y="576" width="160" height="80" rx="12" ry="12" fill="url(#mx-gradient-ffffff-1-ffffff-1-s-2886-0)" stroke="#000000" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 158px; height: 1px; padding-top: 616px; margin-left: 113px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 14px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; ">gotest 测试用例<br style="font-size: 14px;" />(.go)</div></div></div></foreignObject></g><rect x="352" y="576" width="160" height="80" rx="12" ry="12" fill="url(#mx-gradient-ffffff-1-ffffff-1-s-2886-0)" stroke="#000000" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 158px; height: 1px; padding-top: 616px; margin-left: 353px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 14px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; ">pytest 测试用例<br style="font-size: 14px;" />(.py)</div></div></div></foreignObject></g><rect x="112" y="376" width="160" height="80" rx="12" ry="12" fill="url(#mx-gradient-ffffff-1-ffffff-1-s-2886-0)" stroke="#000000" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 158px; height: 1px; padding-top: 416px; margin-left: 113px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 14px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; ">JSON 测试用例<br style="font-size: 14px;" />(.json)</div></div></div></foreignObject></g><rect x="352" y="376" width="160" height="80" rx="12" ry="12" fill="url(#mx-gradient-ffffff-1-ffffff-1-s-2886-0)" stroke="#000000" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 158px; height: 1px; padding-top: 416px; margin-left: 353px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 14px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; ">YAML 测试用例<br style="font-size: 14px;" />(.yaml)</div></div></div></foreignObject></g><path d="M 437 556.5 L 447.5 556.5 L 432 575.5 L 416.5 556.5 L 427 556.5 L 427 475.5 L 416.5 475.5 L 432 456.5 L 447.5 475.5 L 437 475.5 Z" fill="#ffffff" stroke="#000000" stroke-linejoin="round" stroke-miterlimit="10" pointer-events="all"/><path d="M 286.98 562.55 L 295.71 568.37 L 272.28 575.58 L 269.92 551.18 L 278.66 557 L 337.02 469.45 L 328.29 463.63 L 351.72 456.42 L 354.08 480.82 L 345.34 475 Z" fill="#ffffff" stroke="#000000" stroke-linejoin="round" stroke-miterlimit="10" pointer-events="all"/><path d="M 345.34 557 L 354.08 551.18 L 351.72 575.58 L 328.29 568.37 L 337.02 562.55 L 278.66 475 L 269.92 480.82 L 272.28 456.42 L 295.71 463.63 L 286.98 469.45 Z" fill="#ffffff" stroke="#000000" stroke-linejoin="round" stroke-miterlimit="10" pointer-events="all"/><path d="M 187 296.5 L 197 296.5 L 197 356.5 L 207.5 356.5 L 192 375.5 L 176.5 356.5 L 187 356.5 Z" fill="#ffffff" stroke="#000000" stroke-linejoin="round" stroke-miterlimit="10" pointer-events="all"/><rect x="112" y="176" width="160" height="80" rx="12" ry="12" fill="url(#mx-gradient-ffffff-1-ffffff-1-s-2886-0)" stroke="#000000" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 158px; height: 1px; padding-top: 216px; margin-left: 113px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 14px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; "><span style="color: rgb(37, 37, 37); font-family: Roboto, arial, sans-serif; text-align: start; font-size: 14px;"><font style="font-size: 14px;">Swagger 脚本文件<br style="font-size: 14px;" />(.json / .yaml)<br style="font-size: 14px;" /></font></span></div></div></div></foreignObject></g><rect x="262" y="16" width="100" height="40" fill="none" stroke="none" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 98px; height: 1px; padding-top: 36px; margin-left: 263px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 16px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; font-weight: bold; white-space: normal; word-wrap: normal; ">外部脚本文件</div></div></div></foreignObject></g><rect x="230.75" y="336" width="162.5" height="40" fill="none" stroke="none" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 161px; height: 1px; padding-top: 356px; margin-left: 232px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 16px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; font-weight: bold; white-space: normal; word-wrap: normal; ">JSON/YAML 测试用例</div></div></div></foreignObject></g><rect x="247" y="656" width="130" height="40" fill="none" stroke="none" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 128px; height: 1px; padding-top: 676px; margin-left: 248px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 16px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; font-weight: bold; white-space: normal; word-wrap: normal; ">代码形态测试用例</div></div></div></foreignObject></g><path d="M 197 556.5 L 207.5 556.5 L 192 575.5 L 176.5 556.5 L 187 556.5 L 187 475.5 L 176.5 475.5 L 192 456.5 L 207.5 475.5 L 197 475.5 Z" fill="#ffffff" stroke="#000000" stroke-linejoin="round" stroke-miterlimit="10" pointer-events="all"/><path d="M 427 296.5 L 437 296.5 L 437 356.5 L 447.5 356.5 L 432 375.5 L 416.5 356.5 L 427 356.5 Z" fill="#ffffff" stroke="#000000" stroke-linejoin="round" stroke-miterlimit="10" pointer-events="all"/><path d="M 332.5 411 L 332.5 400.5 L 351.5 416 L 332.5 431.5 L 332.5 421 L 291.5 421 L 291.5 431.5 L 272.5 416 L 291.5 400.5 L 291.5 411 Z" fill="#ffffff" stroke="#000000" stroke-linejoin="round" stroke-miterlimit="10" pointer-events="all"/><path d="M 332.5 611 L 332.5 600.5 L 351.5 616 L 332.5 631.5 L 332.5 621 L 291.5 621 L 291.5 631.5 L 272.5 616 L 291.5 600.5 L 291.5 611 Z" fill="#ffffff" stroke="#000000" stroke-linejoin="round" stroke-miterlimit="10" pointer-events="all"/></g></svg> \ No newline at end of file diff --git a/hrp/internal/convert/converter.go b/hrp/internal/convert/converter.go index ac6831cc..8733614c 100644 --- a/hrp/internal/convert/converter.go +++ b/hrp/internal/convert/converter.go @@ -117,42 +117,46 @@ func NewTCaseConverter(path string) (tCaseConverter *TCaseConverter) { case ".har": caseHAR := new(CaseHar) err = builtin.LoadFile(path, caseHAR) - if err == nil && !reflect.DeepEqual(*caseHAR, CaseHar{}) { + if err == nil && !reflect.ValueOf(*caseHAR).IsZero() { tCaseConverter.InputType = InputTypeHAR tCaseConverter.CaseHAR = caseHAR } case ".json": tCase := new(hrp.TCase) err = builtin.LoadFile(path, tCase) - if err == nil && !reflect.DeepEqual(*tCase, hrp.TCase{}) { + if err == nil && !reflect.ValueOf(*tCase).IsZero() { tCaseConverter.InputType = InputTypeJSON tCaseConverter.TCase = tCase break } casePostman := new(CasePostman) err = builtin.LoadFile(path, casePostman) - if err == nil && !reflect.DeepEqual(*casePostman, CasePostman{}) { + // deal with postman field name conflict with swagger + descriptionBackup := casePostman.Info.Description + casePostman.Info.Description = "" + if err == nil && !reflect.ValueOf(*casePostman).IsZero() { tCaseConverter.InputType = InputTypePostman + casePostman.Info.Description = descriptionBackup tCaseConverter.CasePostman = casePostman break } caseSwagger := new(spec.Swagger) err = builtin.LoadFile(path, caseSwagger) - if err == nil && !reflect.DeepEqual(*caseSwagger, spec.Swagger{}) { + if err == nil && !reflect.ValueOf(*caseSwagger).IsZero() { tCaseConverter.InputType = InputTypeSwagger tCaseConverter.CaseSwagger = caseSwagger } case ".yaml", ".yml": tCase := new(hrp.TCase) err = builtin.LoadFile(path, tCase) - if err == nil && !reflect.DeepEqual(*tCase, hrp.TCase{}) { + if err == nil && !reflect.ValueOf(*tCase).IsZero() { tCaseConverter.InputType = InputTypeYAML tCaseConverter.TCase = tCase break } caseSwagger := new(spec.Swagger) err = builtin.LoadFile(path, caseSwagger) - if err == nil && !reflect.DeepEqual(*caseSwagger, spec.Swagger{}) { + if err == nil && !reflect.ValueOf(*caseSwagger).IsZero() { tCaseConverter.InputType = InputTypeSwagger tCaseConverter.CaseSwagger = caseSwagger } @@ -243,13 +247,14 @@ type ICaseConverter interface { ToPyTest() (string, error) } -func LoadConverters(outputType OutputType, outputDir, profilePath string, args []string) []ICaseConverter { +func Run(outputType OutputType, outputDir, profilePath string, args []string) { // report event sdk.SendEvent(sdk.EventTracking{ Category: "ConvertTests", Action: fmt.Sprintf("hrp convert --to-%s", outputType.String()), }) + // identify input and load converters var iCaseConverters []ICaseConverter for _, arg := range args { tCaseConverter := NewTCaseConverter(arg) @@ -279,10 +284,8 @@ func LoadConverters(outputType OutputType, outputDir, profilePath string, args [ Msg("unknown case type, ignore!") } } - return iCaseConverters -} -func Run(iCaseConverters []ICaseConverter) { + // start converting var outputFiles []string var err error for _, iCaseConverter := range iCaseConverters { diff --git a/hrp/internal/convert/converter_har.go b/hrp/internal/convert/converter_har.go index d34717c9..1ee513ae 100644 --- a/hrp/internal/convert/converter_har.go +++ b/hrp/internal/convert/converter_har.go @@ -3,7 +3,6 @@ package convert import ( "encoding/base64" "fmt" - "github.com/httprunner/httprunner/v4/hrp/internal/builtin" "net/url" "sort" "strings" @@ -13,6 +12,7 @@ import ( "github.com/rs/zerolog/log" "github.com/httprunner/httprunner/v4/hrp" + "github.com/httprunner/httprunner/v4/hrp/internal/builtin" "github.com/httprunner/httprunner/v4/hrp/internal/json" ) diff --git a/hrp/internal/convert/converter_postman_test.go b/hrp/internal/convert/converter_postman_test.go index 72994794..9e8ad126 100644 --- a/hrp/internal/convert/converter_postman_test.go +++ b/hrp/internal/convert/converter_postman_test.go @@ -7,9 +7,9 @@ import ( ) var ( - collectionPath = "../../../examples/data/postman2case/demo.json" - collectionProfileOverridePath = "../../../examples/data/postman2case/profile_override.yml" - collectionProfilePath = "../../../examples/data/postman2case/profile.yml" + collectionPath = "../../../examples/data/postman/postman_collection.json" + collectionProfileOverridePath = "../../../examples/data/postman/profile_override.yml" + collectionProfilePath = "../../../examples/data/postman/profile.yml" ) var converterPostman = NewConverterPostman(NewTCaseConverter(collectionPath)) diff --git a/hrp/internal/convert/har2case/README.md b/hrp/internal/convert/har2case/README.md deleted file mode 100644 index 08c0b4dc..00000000 --- a/hrp/internal/convert/har2case/README.md +++ /dev/null @@ -1,9 +0,0 @@ -# har2case - -Convert HAR(HTTP Archive) to YAML/JSON testcases for HttpRunner and HttpRunner+. - -## Install - -## Quick Start - -## Examples diff --git a/hrp/internal/convert/har2case/core.go b/hrp/internal/convert/har2case/core.go deleted file mode 100644 index 25824855..00000000 --- a/hrp/internal/convert/har2case/core.go +++ /dev/null @@ -1,385 +0,0 @@ -package har2case - -import ( - "encoding/base64" - "fmt" - "net/url" - "path/filepath" - "sort" - "strings" - - "github.com/pkg/errors" - "github.com/rs/zerolog/log" - - "github.com/httprunner/httprunner/v4/hrp" - "github.com/httprunner/httprunner/v4/hrp/internal/builtin" - "github.com/httprunner/httprunner/v4/hrp/internal/json" - "github.com/httprunner/httprunner/v4/hrp/internal/sdk" -) - -const ( - suffixJSON = ".json" - suffixYAML = ".yaml" -) - -func NewHAR(path string) *har { - return &har{ - path: path, - } -} - -type har struct { - path string - filterStr string - excludeStr string - profile map[string]interface{} - outputDir string -} - -func (h *har) SetProfile(path string) { - log.Info().Str("path", path).Msg("set profile") - h.profile = make(map[string]interface{}) - err := builtin.LoadFile(path, h.profile) - if err != nil { - log.Warn().Str("path", path). - Msg("invalid profile format, ignore!") - } -} - -func (h *har) SetOutputDir(dir string) { - log.Info().Str("dir", dir).Msg("set output directory") - h.outputDir = dir -} - -func (h *har) GenJSON() (jsonPath string, err error) { - event := sdk.EventTracking{ - Category: "ConvertTests", - Action: "hrp har2case --to-json", - } - // report start event - go sdk.SendEvent(event) - // report running timing event - defer sdk.SendEvent(event.StartTiming("execution")) - - tCase, err := h.makeTestCase() - if err != nil { - return "", err - } - jsonPath = h.genOutputPath(suffixJSON) - err = builtin.Dump2JSON(tCase, jsonPath) - return -} - -func (h *har) GenYAML() (yamlPath string, err error) { - event := sdk.EventTracking{ - Category: "ConvertTests", - Action: "hrp har2case --to-yaml", - } - // report start event - go sdk.SendEvent(event) - // report running timing event - defer sdk.SendEvent(event.StartTiming("execution")) - - tCase, err := h.makeTestCase() - if err != nil { - return "", err - } - yamlPath = h.genOutputPath(suffixYAML) - err = builtin.Dump2YAML(tCase, yamlPath) - return -} - -func (h *har) makeTestCase() (*hrp.TCase, error) { - teststeps, err := h.prepareTestSteps() - if err != nil { - return nil, err - } - - tCase := &hrp.TCase{ - Config: h.prepareConfig(), - TestSteps: teststeps, - } - return tCase, nil -} - -func (h *har) load() (*Har, error) { - har := &Har{} - err := builtin.LoadFile(h.path, har) - if err != nil { - return nil, errors.Wrap(err, "load har failed") - } - return har, nil -} - -func (h *har) prepareConfig() *hrp.TConfig { - return hrp.NewConfig("testcase description"). - SetVerifySSL(false) -} - -func (h *har) prepareTestSteps() ([]*hrp.TStep, error) { - har, err := h.load() - if err != nil { - return nil, err - } - - var steps []*hrp.TStep - for _, entry := range har.Log.Entries { - step, err := h.prepareTestStep(&entry) - if err != nil { - return nil, err - } - steps = append(steps, step) - } - - return steps, nil -} - -func (h *har) prepareTestStep(entry *Entry) (*hrp.TStep, error) { - log.Info(). - Str("method", entry.Request.Method). - Str("url", entry.Request.URL). - Msg("convert teststep") - - step := &tStep{ - TStep: hrp.TStep{ - Request: &hrp.Request{}, - Validators: make([]interface{}, 0), - }, - profile: h.profile, - } - if err := step.makeRequestMethod(entry); err != nil { - return nil, err - } - if err := step.makeRequestURL(entry); err != nil { - return nil, err - } - if err := step.makeRequestParams(entry); err != nil { - return nil, err - } - if err := step.makeRequestCookies(entry); err != nil { - return nil, err - } - if err := step.makeRequestHeaders(entry); err != nil { - return nil, err - } - if err := step.makeRequestBody(entry); err != nil { - return nil, err - } - if err := step.makeValidate(entry); err != nil { - return nil, err - } - return &step.TStep, nil -} - -type tStep struct { - hrp.TStep - profile map[string]interface{} -} - -func (s *tStep) makeRequestMethod(entry *Entry) error { - s.Request.Method = hrp.HTTPMethod(entry.Request.Method) - return nil -} - -func (s *tStep) makeRequestURL(entry *Entry) error { - u, err := url.Parse(entry.Request.URL) - if err != nil { - log.Error().Err(err).Msg("make request url failed") - return err - } - s.Request.URL = fmt.Sprintf("%s://%s", u.Scheme, u.Host+u.Path) - return nil -} - -func (s *tStep) makeRequestParams(entry *Entry) error { - s.Request.Params = make(map[string]interface{}) - for _, param := range entry.Request.QueryString { - s.Request.Params[param.Name] = param.Value - } - return nil -} - -func (s *tStep) makeRequestCookies(entry *Entry) error { - s.Request.Cookies = make(map[string]string) - cookies, ok := s.profile["cookies"] - if ok { - // use cookies from profile - cookies, ok := cookies.(map[string]interface{}) - if ok { - for k, v := range cookies { - s.Request.Cookies[k] = fmt.Sprintf("%v", v) - } - return nil - } - log.Warn().Interface("cookies", cookies). - Msg("cookies from profile is not a map, ignore!") - } - - // use cookies from har - for _, cookie := range entry.Request.Cookies { - s.Request.Cookies[cookie.Name] = cookie.Value - } - return nil -} - -func (s *tStep) makeRequestHeaders(entry *Entry) error { - s.Request.Headers = make(map[string]string) - headers, ok := s.profile["headers"] - if ok { - // use headers from profile - cookies, ok := headers.(map[string]interface{}) - if ok { - for k, v := range cookies { - s.Request.Headers[k] = fmt.Sprintf("%v", v) - } - return nil - } - log.Warn().Interface("headers", headers). - Msg("headers from profile is not a map, ignore!") - } - - // use headers from har - for _, header := range entry.Request.Headers { - if strings.EqualFold(header.Name, "cookie") { - continue - } - s.Request.Headers[header.Name] = header.Value - } - return nil -} - -func (s *tStep) makeRequestBody(entry *Entry) error { - mimeType := entry.Request.PostData.MimeType - if mimeType == "" { - // GET/HEAD/DELETE without body - return nil - } - - // POST/PUT with body - if strings.HasPrefix(mimeType, "application/json") { - // post json - var body interface{} - if entry.Request.PostData.Text == "" { - body = nil - } else { - err := json.Unmarshal([]byte(entry.Request.PostData.Text), &body) - if err != nil { - log.Error().Err(err).Msg("make request body failed") - return err - } - } - s.Request.Body = body - } else if strings.HasPrefix(mimeType, "application/x-www-form-urlencoded") { - // post form - var paramsList []string - for _, param := range entry.Request.PostData.Params { - paramsList = append(paramsList, fmt.Sprintf("%s=%s", param.Name, param.Value)) - } - s.Request.Body = strings.Join(paramsList, "&") - } else if strings.HasPrefix(mimeType, "text/plain") { - // post raw data - s.Request.Body = entry.Request.PostData.Text - } else { - // TODO - log.Error().Msgf("makeRequestBody: Not implemented for mimeType %s", mimeType) - } - return nil -} - -func (s *tStep) makeValidate(entry *Entry) error { - // make validator for response status code - s.Validators = append(s.Validators, hrp.Validator{ - Check: "status_code", - Assert: "equals", - Expect: entry.Response.Status, - Message: "assert response status code", - }) - - // make validators for response headers - for _, header := range entry.Response.Headers { - // assert Content-Type - if strings.EqualFold(header.Name, "Content-Type") { - s.Validators = append(s.Validators, hrp.Validator{ - Check: "headers.\"Content-Type\"", - Assert: "equals", - Expect: header.Value, - Message: "assert response header Content-Type", - }) - } - } - - // make validators for response body - respBody := entry.Response.Content - if respBody.Text == "" { - // response body is empty - return nil - } - if strings.HasPrefix(respBody.MimeType, "application/json") { - var data []byte - var err error - // response body is json - if respBody.Encoding == "base64" { - // decode base64 text - data, err = base64.StdEncoding.DecodeString(respBody.Text) - if err != nil { - return errors.Wrap(err, "decode base64 error") - } - } else if respBody.Encoding == "" { - // no encoding - data = []byte(respBody.Text) - } else { - // other encoding type - return nil - } - // convert to json - var body interface{} - if err = json.Unmarshal(data, &body); err != nil { - return errors.Wrap(err, "json.Unmarshal body error") - } - jsonBody, ok := body.(map[string]interface{}) - if !ok { - return fmt.Errorf("response body is not json, not matched with MimeType") - } - - // response body is json - keys := make([]string, 0, len(jsonBody)) - for k := range jsonBody { - keys = append(keys, k) - } - // sort map keys to keep validators in stable order - sort.Strings(keys) - for _, key := range keys { - value := jsonBody[key] - switch v := value.(type) { - case map[string]interface{}: - continue - case []interface{}: - continue - default: - s.Validators = append(s.Validators, hrp.Validator{ - Check: fmt.Sprintf("body.%s", key), - Assert: "equals", - Expect: v, - Message: fmt.Sprintf("assert response body %s", key), - }) - } - } - } - - return nil -} - -func (h *har) genOutputPath(suffix string) string { - file := getFilenameWithoutExtension(h.path) + suffix - if h.outputDir != "" { - return filepath.Join(h.outputDir, file) - } else { - return filepath.Join(filepath.Dir(h.path), file) - } -} - -func getFilenameWithoutExtension(path string) string { - base := filepath.Base(path) - ext := filepath.Ext(base) - return base[0 : len(base)-len(ext)] -} diff --git a/hrp/internal/convert/har2case/core_test.go b/hrp/internal/convert/har2case/core_test.go deleted file mode 100644 index 0fc6a3cb..00000000 --- a/hrp/internal/convert/har2case/core_test.go +++ /dev/null @@ -1,383 +0,0 @@ -package har2case - -import ( - "testing" - - "github.com/stretchr/testify/assert" - - "github.com/httprunner/httprunner/v4/hrp" -) - -var ( - harPath = "../../../../examples/data/har/demo.har" - harPath2 = "../../../../examples/data/har/postman-echo.har" - profilePath = "../../../../examples/data/har/profile_override.yml" -) - -func TestGenJSON(t *testing.T) { - jsonPath, err := NewHAR(harPath).GenJSON() - if !assert.NoError(t, err) { - t.Fatal() - } - if !assert.NotEmpty(t, jsonPath) { - t.Fatal() - } -} - -func TestGenYAML(t *testing.T) { - yamlPath, err := NewHAR(harPath2).GenYAML() - if !assert.NoError(t, err) { - t.Fatal() - } - if !assert.NotEmpty(t, yamlPath) { - t.Fatal() - } -} - -func TestLoadHAR(t *testing.T) { - har := NewHAR(harPath) - h, err := har.load() - if !assert.NoError(t, err) { - t.Fatal() - } - if !assert.Equal(t, "GET", h.Log.Entries[0].Request.Method) { - t.Fatal() - } - if !assert.Equal(t, "POST", h.Log.Entries[1].Request.Method) { - t.Fatal() - } -} - -func TestLoadHARWithProfile(t *testing.T) { - har := NewHAR(harPath) - har.SetProfile(profilePath) - _, err := har.load() - if !assert.NoError(t, err) { - t.Fatal() - } - - if !assert.Equal(t, - map[string]interface{}{"Content-Type": "application/x-www-form-urlencoded"}, - har.profile["headers"]) { - t.Fatal() - } - if !assert.Equal(t, - map[string]interface{}{"UserName": "debugtalk"}, - har.profile["cookies"]) { - t.Fatal() - } -} - -func TestMakeTestCase(t *testing.T) { - har := NewHAR(harPath) - tCase, err := har.makeTestCase() - if !assert.NoError(t, err) { - t.Fatal() - } - - // make request method - if !assert.EqualValues(t, "GET", tCase.TestSteps[0].Request.Method) { - t.Fatal() - } - if !assert.EqualValues(t, "POST", tCase.TestSteps[1].Request.Method) { - t.Fatal() - } - - // make request url - if !assert.Equal(t, "https://postman-echo.com/get", tCase.TestSteps[0].Request.URL) { - t.Fatal() - } - if !assert.Equal(t, "https://postman-echo.com/post", tCase.TestSteps[1].Request.URL) { - t.Fatal() - } - - // make request params - if !assert.Equal(t, "HDnY8", tCase.TestSteps[0].Request.Params["foo1"]) { - t.Fatal() - } - - // make request cookies - if !assert.NotEmpty(t, tCase.TestSteps[1].Request.Cookies["sails.sid"]) { - t.Fatal() - } - - // make request headers - if !assert.Equal(t, "HttpRunnerPlus", tCase.TestSteps[0].Request.Headers["User-Agent"]) { - t.Fatal() - } - if !assert.Equal(t, "postman-echo.com", tCase.TestSteps[0].Request.Headers["Host"]) { - t.Fatal() - } - - // make request data - if !assert.Equal(t, nil, tCase.TestSteps[0].Request.Body) { - t.Fatal() - } - if !assert.Equal(t, map[string]interface{}{"foo1": "HDnY8", "foo2": 12.3}, tCase.TestSteps[1].Request.Body) { - t.Fatal() - } - if !assert.Equal(t, "foo1=HDnY8&foo2=12.3", tCase.TestSteps[2].Request.Body) { - t.Fatal() - } - - // make validators - validator, ok := tCase.TestSteps[0].Validators[0].(hrp.Validator) - if !ok || !assert.Equal(t, "status_code", validator.Check) { - t.Fatal() - } - validator, ok = tCase.TestSteps[0].Validators[1].(hrp.Validator) - if !ok || !assert.Equal(t, "headers.\"Content-Type\"", validator.Check) { - t.Fatal() - } - validator, ok = tCase.TestSteps[0].Validators[2].(hrp.Validator) - if !ok || !assert.Equal(t, "body.url", validator.Check) { - t.Fatal() - } -} - -func TestGetFilenameWithoutExtension(t *testing.T) { - filename := getFilenameWithoutExtension(harPath2) - if !assert.Equal(t, "postman-echo", filename) { - t.Fatal() - } -} - -func TestMakeRequestURL(t *testing.T) { - har := NewHAR("") - entry := &Entry{ - Request: Request{ - URL: "http://127.0.0.1:8080/api/login", - }, - } - step, err := har.prepareTestStep(entry) - if !assert.NoError(t, err) { - t.Fatal() - } - - if !assert.Equal(t, "http://127.0.0.1:8080/api/login", step.Request.URL) { - t.Fatal() - } -} - -func TestMakeRequestHeaders(t *testing.T) { - har := NewHAR("") - entry := &Entry{ - Request: Request{ - Method: "POST", - Headers: []NVP{ - {Name: "Content-Type", Value: "application/json; charset=utf-8"}, - }, - }, - } - step, err := har.prepareTestStep(entry) - if !assert.NoError(t, err) { - t.Fatal() - } - - if !assert.Equal(t, map[string]string{ - "Content-Type": "application/json; charset=utf-8", - }, step.Request.Headers) { - t.Fatal() - } -} - -func TestMakeRequestHeadersWithProfile(t *testing.T) { - har := NewHAR("") - har.SetProfile(profilePath) - entry := &Entry{ - Request: Request{ - Method: "POST", - Headers: []NVP{ - {Name: "Content-Type", Value: "application/json; charset=utf-8"}, - }, - }, - } - step, err := har.prepareTestStep(entry) - if !assert.NoError(t, err) { - t.Fatal() - } - - if !assert.Equal(t, map[string]string{ - "Content-Type": "application/x-www-form-urlencoded", - }, step.Request.Headers) { - t.Fatal() - } -} - -func TestMakeRequestCookies(t *testing.T) { - har := NewHAR("") - entry := &Entry{ - Request: Request{ - Method: "POST", - Cookies: []Cookie{ - {Name: "abc", Value: "123"}, - {Name: "UserName", Value: "leolee"}, - }, - }, - } - step, err := har.prepareTestStep(entry) - if !assert.NoError(t, err) { - t.Fatal() - } - - if !assert.Equal(t, map[string]string{ - "abc": "123", - "UserName": "leolee", - }, step.Request.Cookies) { - t.Fatal() - } -} - -func TestMakeRequestCookiesWithProfile(t *testing.T) { - har := NewHAR("") - har.SetProfile(profilePath) - entry := &Entry{ - Request: Request{ - Method: "POST", - Cookies: []Cookie{ - {Name: "abc", Value: "123"}, - {Name: "UserName", Value: "leolee"}, - }, - }, - } - step, err := har.prepareTestStep(entry) - if !assert.NoError(t, err) { - t.Fatal() - } - - if !assert.Equal(t, map[string]string{ - "UserName": "debugtalk", - }, step.Request.Cookies) { - t.Fatal() - } -} - -func TestMakeRequestDataParams(t *testing.T) { - har := NewHAR("") - entry := &Entry{ - Request: Request{ - Method: "POST", - PostData: PostData{ - MimeType: "application/x-www-form-urlencoded; charset=utf-8", - Params: []PostParam{ - {Name: "a", Value: "1"}, - {Name: "b", Value: "2"}, - }, - }, - }, - } - step, err := har.prepareTestStep(entry) - if !assert.NoError(t, err) { - t.Fatal() - } - - if !assert.Equal(t, "a=1&b=2", step.Request.Body) { - t.Fatal() - } -} - -func TestMakeRequestDataJSON(t *testing.T) { - har := NewHAR("") - entry := &Entry{ - Request: Request{ - Method: "POST", - PostData: PostData{ - MimeType: "application/json; charset=utf-8", - Text: "{\"a\":\"1\",\"b\":\"2\"}", - }, - }, - } - step, err := har.prepareTestStep(entry) - if !assert.NoError(t, err) { - t.Fatal() - } - - if !assert.Equal(t, map[string]interface{}{"a": "1", "b": "2"}, step.Request.Body) { - t.Fatal() - } -} - -func TestMakeRequestDataTextEmpty(t *testing.T) { - har := NewHAR("") - entry := &Entry{ - Request: Request{ - Method: "POST", - PostData: PostData{ - MimeType: "application/json; charset=utf-8", - Text: "", - }, - }, - } - step, err := har.prepareTestStep(entry) - if !assert.NoError(t, err) { - t.Fatal() - } - - if !assert.Equal(t, nil, step.Request.Body) { // TODO - t.Fatal() - } -} - -func TestMakeValidate(t *testing.T) { - har := NewHAR("") - entry := &Entry{ - Response: Response{ - Status: 200, - Headers: []NVP{ - {Name: "Content-Type", Value: "application/json; charset=utf-8"}, - }, - Content: Content{ - Size: 71, - MimeType: "application/json; charset=utf-8", - // map[Code:200 IsSuccess:true Message:<nil> Value:map[BlnResult:true]] - Text: "eyJJc1N1Y2Nlc3MiOnRydWUsIkNvZGUiOjIwMCwiTWVzc2FnZSI6bnVsbCwiVmFsdWUiOnsiQmxuUmVzdWx0Ijp0cnVlfX0=", - Encoding: "base64", - }, - }, - } - step, err := har.prepareTestStep(entry) - if !assert.NoError(t, err) { - t.Fatal() - } - validator, ok := step.Validators[0].(hrp.Validator) - if !ok { - t.Fatal() - } - if !assert.Equal(t, validator, - hrp.Validator{ - Check: "status_code", - Expect: 200, - Assert: "equals", - Message: "assert response status code", - }) { - t.Fatal() - } - - validator, ok = step.Validators[1].(hrp.Validator) - if !ok { - t.Fatal() - } - if !assert.Equal(t, validator, - hrp.Validator{ - Check: "headers.\"Content-Type\"", - Expect: "application/json; charset=utf-8", - Assert: "equals", - Message: "assert response header Content-Type", - }) { - t.Fatal() - } - - validator, ok = step.Validators[2].(hrp.Validator) - if !ok { - t.Fatal() - } - if !assert.Equal(t, validator, - hrp.Validator{ - Check: "body.Code", - Expect: float64(200), // TODO - Assert: "equals", - Message: "assert response body Code", - }) { - t.Fatal() - } -} diff --git a/hrp/internal/convert/har2case/har.go b/hrp/internal/convert/har2case/har.go deleted file mode 100644 index 6b98839a..00000000 --- a/hrp/internal/convert/har2case/har.go +++ /dev/null @@ -1,340 +0,0 @@ -package har2case - -import "time" - -/* -HTTP Archive (HAR) format -https://w3c.github.io/web-performance/specs/HAR/Overview.html -this file is copied from https://github.com/mrichman/hargo/blob/master/types.go -*/ - -// Har is a container type for deserialization -type Har struct { - Log Log `json:"log"` -} - -// Log represents the root of the exported data. This object MUST be present and its name MUST be "log". -type Log struct { - // The object contains the following name/value pairs: - - // Required. Version number of the format. - Version string `json:"version"` - // Required. An object of type creator that contains the name and version - // information of the log creator application. - Creator Creator `json:"creator"` - // Optional. An object of type browser that contains the name and version - // information of the user agent. - Browser Browser `json:"browser"` - // Optional. An array of objects of type page, each representing one exported - // (tracked) page. Leave out this field if the application does not support - // grouping by pages. - Pages []Page `json:"pages,omitempty"` - // Required. An array of objects of type entry, each representing one - // exported (tracked) HTTP request. - Entries []Entry `json:"entries"` - // Optional. A comment provided by the user or the application. Sorting - // entries by startedDateTime (starting from the oldest) is preferred way how - // to export data since it can make importing faster. However the reader - // application should always make sure the array is sorted (if required for - // the import). - Comment string `json:"comment"` -} - -// Creator contains information about the log creator application -type Creator struct { - // Required. The name of the application that created the log. - Name string `json:"name"` - // Required. The version number of the application that created the log. - Version string `json:"version"` - // Optional. A comment provided by the user or the application. - Comment string `json:"comment,omitempty"` -} - -// Browser that created the log -type Browser struct { - // Required. The name of the browser that created the log. - Name string `json:"name"` - // Required. The version number of the browser that created the log. - Version string `json:"version"` - // Optional. A comment provided by the user or the browser. - Comment string `json:"comment"` -} - -// Page object for every exported web page and one <entry> object for every HTTP request. -// In case when an HTTP trace tool isn't able to group requests by a page, -// the <pages> object is empty and individual requests doesn't have a parent page. -type Page struct { - /* There is one <page> object for every exported web page and one <entry> - object for every HTTP request. In case when an HTTP trace tool isn't able to - group requests by a page, the <pages> object is empty and individual - requests doesn't have a parent page. - */ - - // Date and time stamp for the beginning of the page load - // (ISO 8601 YYYY-MM-DDThh:mm:ss.sTZD, e.g. 2009-07-24T19:20:30.45+01:00). - StartedDateTime string `json:"startedDateTime"` - // Unique identifier of a page within the . Entries use it to refer the parent page. - ID string `json:"id"` - // Page title. - Title string `json:"title"` - // Detailed timing info about page load. - PageTiming PageTiming `json:"pageTiming"` - // (new in 1.2) A comment provided by the user or the application. - Comment string `json:"comment,omitempty"` -} - -// PageTiming describes timings for various events (states) fired during the page load. -// All times are specified in milliseconds. If a time info is not available appropriate field is set to -1. -type PageTiming struct { - // Content of the page loaded. Number of milliseconds since page load started - // (page.startedDateTime). Use -1 if the timing does not apply to the current - // request. - // Depeding on the browser, onContentLoad property represents DOMContentLoad - // event or document.readyState == interactive. - OnContentLoad int `json:"onContentLoad"` - // Page is loaded (onLoad event fired). Number of milliseconds since page - // load started (page.startedDateTime). Use -1 if the timing does not apply - // to the current request. - OnLoad int `json:"onLoad"` - // (new in 1.2) A comment provided by the user or the application. - Comment string `json:"comment"` -} - -// Entry is a unique, optional Reference to the parent page. -// Leave out this field if the application does not support grouping by pages. -type Entry struct { - Pageref string `json:"pageref,omitempty"` - // Date and time stamp of the request start - // (ISO 8601 YYYY-MM-DDThh:mm:ss.sTZD). - StartedDateTime string `json:"startedDateTime"` - // Total elapsed time of the request in milliseconds. This is the sum of all - // timings available in the timings object (i.e. not including -1 values) . - Time float32 `json:"time"` - // Detailed info about the request. - Request Request `json:"request"` - // Detailed info about the response. - Response Response `json:"response"` - // Info about cache usage. - Cache Cache `json:"cache"` - // Detailed timing info about request/response round trip. - PageTimings PageTimings `json:"pageTimings"` - // optional (new in 1.2) IP address of the server that was connected - // (result of DNS resolution). - ServerIPAddress string `json:"serverIPAddress,omitempty"` - // optional (new in 1.2) Unique ID of the parent TCP/IP connection, can be - // the client port number. Note that a port number doesn't have to be unique - // identifier in cases where the port is shared for more connections. If the - // port isn't available for the application, any other unique connection ID - // can be used instead (e.g. connection index). Leave out this field if the - // application doesn't support this info. - Connection string `json:"connection,omitempty"` - // (new in 1.2) A comment provided by the user or the application. - Comment string `json:"comment,omitempty"` -} - -// Request contains detailed info about performed request. -type Request struct { - // Request method (GET, POST, ...). - Method string `json:"method"` - // Absolute URL of the request (fragments are not included). - URL string `json:"url"` - // Request HTTP Version. - HTTPVersion string `json:"httpVersion"` - // List of cookie objects. - Cookies []Cookie `json:"cookies"` - // List of header objects. - Headers []NVP `json:"headers"` - // List of query parameter objects. - QueryString []NVP `json:"queryString"` - // Posted data. - PostData PostData `json:"postData"` - // Total number of bytes from the start of the HTTP request message until - // (and including) the double CRLF before the body. Set to -1 if the info - // is not available. - HeaderSize int `json:"headerSize"` - // Size of the request body (POST data payload) in bytes. Set to -1 if the - // info is not available. - BodySize int `json:"bodySize"` - // (new in 1.2) A comment provided by the user or the application. - Comment string `json:"comment"` -} - -// Response contains detailed info about the response. -type Response struct { - // Response status. - Status int `json:"status"` - // Response status description. - StatusText string `json:"statusText"` - // Response HTTP Version. - HTTPVersion string `json:"httpVersion"` - // List of cookie objects. - Cookies []Cookie `json:"cookies"` - // List of header objects. - Headers []NVP `json:"headers"` - // Details about the response body. - Content Content `json:"content"` - // Redirection target URL from the Location response header. - RedirectURL string `json:"redirectURL"` - // Total number of bytes from the start of the HTTP response message until - // (and including) the double CRLF before the body. Set to -1 if the info is - // not available. - // The size of received response-headers is computed only from headers that - // are really received from the server. Additional headers appended by the - // browser are not included in this number, but they appear in the list of - // header objects. - HeadersSize int `json:"headersSize"` - // Size of the received response body in bytes. Set to zero in case of - // responses coming from the cache (304). Set to -1 if the info is not - // available. - BodySize int `json:"bodySize"` - // optional (new in 1.2) A comment provided by the user or the application. - Comment string `json:"comment,omitempty"` -} - -// Cookie contains list of all cookies (used in <request> and <response> objects). -type Cookie struct { - // The name of the cookie. - Name string `json:"name"` - // The cookie value. - Value string `json:"value"` - // optional The path pertaining to the cookie. - Path string `json:"path,omitempty"` - // optional The host of the cookie. - Domain string `json:"domain,omitempty"` - // optional Cookie expiration time. - // (ISO 8601 YYYY-MM-DDThh:mm:ss.sTZD, e.g. 2009-07-24T19:20:30.123+02:00). - Expires string `json:"expires,omitempty"` - // optional Set to true if the cookie is HTTP only, false otherwise. - HTTPOnly bool `json:"httpOnly,omitempty"` - // optional (new in 1.2) True if the cookie was transmitted over ssl, false - // otherwise. - Secure bool `json:"secure,omitempty"` - // optional (new in 1.2) A comment provided by the user or the application. - Comment bool `json:"comment,omitempty"` -} - -// NVP is simply a name/value pair with a comment -type NVP struct { - Name string `json:"name"` - Value string `json:"value"` - Comment string `json:"comment,omitempty"` -} - -// PostData describes posted data, if any (embedded in <request> object). -type PostData struct { - // Mime type of posted data. - MimeType string `json:"mimeType"` - // List of posted parameters (in case of URL encoded parameters). - Params []PostParam `json:"params"` - // Plain text posted data - Text string `json:"text"` - // optional (new in 1.2) A comment provided by the user or the - // application. - Comment string `json:"comment,omitempty"` -} - -// PostParam is a list of posted parameters, if any (embedded in <postData> object). -type PostParam struct { - // name of a posted parameter. - Name string `json:"name"` - // optional value of a posted parameter or content of a posted file. - Value string `json:"value,omitempty"` - // optional name of a posted file. - FileName string `json:"fileName,omitempty"` - // optional content type of a posted file. - ContentType string `json:"contentType,omitempty"` - // optional (new in 1.2) A comment provided by the user or the application. - Comment string `json:"comment,omitempty"` -} - -// Content describes details about response content (embedded in <response> object). -type Content struct { - // Length of the returned content in bytes. Should be equal to - // response.bodySize if there is no compression and bigger when the content - // has been compressed. - Size int `json:"size"` - // optional Number of bytes saved. Leave out this field if the information - // is not available. - Compression int `json:"compression,omitempty"` - // MIME type of the response text (value of the Content-Type response - // header). The charset attribute of the MIME type is included (if - // available). - MimeType string `json:"mimeType"` - // optional Response body sent from the server or loaded from the browser - // cache. This field is populated with textual content only. The text field - // is either HTTP decoded text or a encoded (e.g. "base64") representation of - // the response body. Leave out this field if the information is not - // available. - Text string `json:"text,omitempty"` - // optional (new in 1.2) Encoding used for response text field e.g - // "base64". Leave out this field if the text field is HTTP decoded - // (decompressed & unchunked), than trans-coded from its original character - // set into UTF-8. - Encoding string `json:"encoding,omitempty"` - // optional (new in 1.2) A comment provided by the user or the application. - Comment string `json:"comment,omitempty"` -} - -// Cache contains info about a request coming from browser cache. -type Cache struct { - // optional State of a cache entry before the request. Leave out this field - // if the information is not available. - BeforeRequest CacheObject `json:"beforeRequest,omitempty"` - // optional State of a cache entry after the request. Leave out this field if - // the information is not available. - AfterRequest CacheObject `json:"afterRequest,omitempty"` - // optional (new in 1.2) A comment provided by the user or the application. - Comment string `json:"comment,omitempty"` -} - -// CacheObject is used by both beforeRequest and afterRequest -type CacheObject struct { - // optional - Expiration time of the cache entry. - Expires string `json:"expires,omitempty"` - // The last time the cache entry was opened. - LastAccess string `json:"lastAccess"` - // Etag - ETag string `json:"eTag"` - // The number of times the cache entry has been opened. - HitCount int `json:"hitCount"` - // optional (new in 1.2) A comment provided by the user or the application. - Comment string `json:"comment,omitempty"` -} - -// PageTimings describes various phases within request-response round trip. -// All times are specified in milliseconds. -type PageTimings struct { - Blocked int `json:"blocked,omitempty"` - // optional - Time spent in a queue waiting for a network connection. Use -1 - // if the timing does not apply to the current request. - DNS int `json:"dns,omitempty"` - // optional - DNS resolution time. The time required to resolve a host name. - // Use -1 if the timing does not apply to the current request. - Connect int `json:"connect,omitempty"` - // optional - Time required to create TCP connection. Use -1 if the timing - // does not apply to the current request. - Send int `json:"send"` - // Time required to send HTTP request to the server. - Wait int `json:"wait"` - // Waiting for a response from the server. - Receive int `json:"receive"` - // Time required to read entire response from the server (or cache). - Ssl int `json:"ssl,omitempty"` - // optional (new in 1.2) - Time required for SSL/TLS negotiation. If this - // field is defined then the time is also included in the connect field (to - // ensure backward compatibility with HAR 1.1). Use -1 if the timing does not - // apply to the current request. - Comment string `json:"comment,omitempty"` - // optional (new in 1.2) - A comment provided by the user or the application. -} - -// TestResult contains results for an individual HTTP request -type TestResult struct { - URL string `json:"url"` - Status int `json:"status"` // 200, 500, etc. - StartTime time.Time `json:"startTime"` - EndTime time.Time `json:"endTime"` - Latency int `json:"latency"` // milliseconds - Method string `json:"method"` - HarFile string `json:"harfile"` -} diff --git a/hrp/testcase.go b/hrp/testcase.go index 41826716..66690340 100644 --- a/hrp/testcase.go +++ b/hrp/testcase.go @@ -193,17 +193,17 @@ func convertValidatorCompat2GoEngine(Validators []interface{}) (err error) { } validatorMap := iValidator.(map[string]interface{}) validator := Validator{} - _, checkExisted := validatorMap["check"] - _, assertExisted := validatorMap["assert"] - _, expectExisted := validatorMap["expect"] + iCheck, checkExisted := validatorMap["check"] + iAssert, assertExisted := validatorMap["assert"] + iExpect, expectExisted := validatorMap["expect"] // validator check priority: Golang > Python engine style if checkExisted && assertExisted && expectExisted { // Golang engine style - validator.Check = validatorMap["check"].(string) - validator.Assert = validatorMap["assert"].(string) - validator.Expect = validatorMap["expect"] - if msg, existed := validatorMap["msg"]; existed { - validator.Message = msg.(string) + validator.Check = iCheck.(string) + validator.Assert = iAssert.(string) + validator.Expect = iExpect + if iMsg, msgExisted := validatorMap["msg"]; msgExisted { + validator.Message = iMsg.(string) } validator.Check = convertCheckExpr(validator.Check) Validators[i] = validator @@ -212,13 +212,16 @@ func convertValidatorCompat2GoEngine(Validators []interface{}) (err error) { if len(validatorMap) == 1 { // Python engine style for assertMethod, iValidatorContent := range validatorMap { - checkAndExpect := iValidatorContent.([]interface{}) - if len(checkAndExpect) != 2 { + validatorContent := iValidatorContent.([]interface{}) + if len(validatorContent) > 3 { return fmt.Errorf("unexpected validator format: %v", validatorMap) } - validator.Check = checkAndExpect[0].(string) + validator.Check = validatorContent[0].(string) validator.Assert = assertMethod - validator.Expect = checkAndExpect[1] + validator.Expect = validatorContent[1] + if len(validatorContent) == 3 { + validator.Message = validatorContent[2].(string) + } } validator.Check = convertCheckExpr(validator.Check) Validators[i] = validator @@ -294,23 +297,26 @@ func convertValidatorCompat2PyEngine(Validators []interface{}) (err error) { if len(validatorMap) == 1 { // Python engine style for _, iValidatorContent := range validatorMap { - checkAndExpect := iValidatorContent.([]interface{}) - if len(checkAndExpect) != 2 { + validatorContent := iValidatorContent.([]interface{}) + if len(validatorContent) > 3 { return fmt.Errorf("unexpected validator format: %v", validatorMap) } } continue } - _, checkExisted := validatorMap["check"] - _, assertExisted := validatorMap["assert"] - _, expectExisted := validatorMap["expect"] + iCheck, checkExisted := validatorMap["check"] + iAssert, assertExisted := validatorMap["assert"] + iExpect, expectExisted := validatorMap["expect"] if checkExisted && assertExisted && expectExisted { // Golang engine style - var iValidatorContent []interface{} - iValidatorContent = append(iValidatorContent, validatorMap["check"]) - iValidatorContent = append(iValidatorContent, validatorMap["expect"]) + var validatorContent []interface{} + validatorContent = append(validatorContent, iCheck) + validatorContent = append(validatorContent, iExpect) + if iMsg, msgExisted := validatorMap["msg"]; msgExisted { + validatorContent = append(validatorContent, iMsg) + } newValidatorMap := make(map[string]interface{}) - newValidatorMap[validatorMap["assert"].(string)] = iValidatorContent + newValidatorMap[iAssert.(string)] = validatorContent Validators[i] = newValidatorMap continue } From 717b08c81c8654ad9a4f19e372a1fe54934bb70c Mon Sep 17 00:00:00 2001 From: debugtalk <mail@debugtalk.com> Date: Tue, 24 May 2022 20:56:27 +0800 Subject: [PATCH 054/109] change: remove byte --- httprunner/thrift/data_convertor.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/httprunner/thrift/data_convertor.py b/httprunner/thrift/data_convertor.py index b25af390..0561ef4a 100644 --- a/httprunner/thrift/data_convertor.py +++ b/httprunner/thrift/data_convertor.py @@ -307,7 +307,7 @@ class MyJSONEncoder(json.JSONEncoder): chunks = self.iterencode(o, _one_shot=True) if not isinstance(chunks, (list, tuple)): chunks = list(chunks) - # add by braver(braver@bytedance.com) + # add by braver # todo: fix 'utf8' codec can't decode byte 0x91 in position 3: invalid start byte" if self.skip_nonutf8_value: # 缺省为false tmp_chunks = [] @@ -324,7 +324,7 @@ class MyJSONEncoder(json.JSONEncoder): class ThriftJSONEncoder(json.JSONEncoder): """ - add by braver(Braver@bytedance.com) + add by braver """ def __init__( @@ -377,7 +377,7 @@ class ThriftJSONEncoder(json.JSONEncoder): chunks = self.iterencode(o, _one_shot=True) if not isinstance(chunks, (list, tuple)): chunks = list(chunks) - # add by braver(braver@bytedance.com) + # add by braver # todo: fix 'utf8' codec can't decode byte 0x91 in position 3: invalid start byte" if self.skip_nonutf8_value: # 缺省为false tmp_chunks = [] From ae36c8fd9adea933c6cab1e8a52412ef6efa5365 Mon Sep 17 00:00:00 2001 From: debugtalk <mail@debugtalk.com> Date: Tue, 24 May 2022 22:23:02 +0800 Subject: [PATCH 055/109] refactor: move base_url to config env --- docs/CHANGELOG.md | 3 ++- examples/demo-with-go-plugin/.env | 3 +++ examples/demo-with-go-plugin/.gitignore | 1 - examples/demo-with-go-plugin/proj.json | 2 +- examples/demo-with-go-plugin/testcases/requests.yml | 7 +++---- examples/demo-with-py-plugin/.env | 3 +++ examples/demo-with-py-plugin/.gitignore | 1 - examples/demo-with-py-plugin/proj.json | 2 +- examples/demo-with-py-plugin/testcases/requests.yml | 7 +++---- examples/demo-without-plugin/.env | 3 +++ examples/demo-without-plugin/.gitignore | 1 - examples/demo-without-plugin/proj.json | 2 +- hrp/config.go | 8 ++++---- hrp/internal/builtin/utils.go | 3 +-- hrp/internal/scaffold/templates/env | 1 + hrp/internal/scaffold/templates/gitignore | 1 - .../scaffold/templates/testcases/demo_requests.yml | 7 +++---- hrp/runner.go | 13 +++++++++++++ hrp/step_request.go | 3 ++- hrp/testcase.go | 13 +++++++++++-- 20 files changed, 55 insertions(+), 29 deletions(-) create mode 100644 examples/demo-with-go-plugin/.env create mode 100644 examples/demo-with-py-plugin/.env create mode 100644 examples/demo-without-plugin/.env diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index a9a7b702..fb603c12 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -1,6 +1,6 @@ # Release History -## v4.1.0 (2022-05-23) +## v4.1.0 (2022-05-24) - feat: add `wiki` sub-command to open httprunner website @@ -8,6 +8,7 @@ - fix #1308: load `.env` file as environment variables - fix #1309: locate plugin file upward recursively until system root dir +- refactor: move base_url to config env ## v4.1.0-beta (2022-05-21) diff --git a/examples/demo-with-go-plugin/.env b/examples/demo-with-go-plugin/.env new file mode 100644 index 00000000..59ecc742 --- /dev/null +++ b/examples/demo-with-go-plugin/.env @@ -0,0 +1,3 @@ +base_url=https://postman-echo.com +USERNAME=debugtalk +PASSWORD=123456 \ No newline at end of file diff --git a/examples/demo-with-go-plugin/.gitignore b/examples/demo-with-go-plugin/.gitignore index 33401380..4c8cb60c 100644 --- a/examples/demo-with-go-plugin/.gitignore +++ b/examples/demo-with-go-plugin/.gitignore @@ -1,4 +1,3 @@ -.env reports/ *.so .vscode/ diff --git a/examples/demo-with-go-plugin/proj.json b/examples/demo-with-go-plugin/proj.json index b8ac381a..3abb9f72 100644 --- a/examples/demo-with-go-plugin/proj.json +++ b/examples/demo-with-go-plugin/proj.json @@ -1,6 +1,6 @@ { "project_name": "demo-with-go-plugin", "project_path": "/Users/debugtalk/MyProjects/HttpRunner-dev/httprunner/examples/demo-with-go-plugin", - "create_time": "2022-05-24T11:10:10.127839+08:00", + "create_time": "2022-05-24T22:21:23.330967+08:00", "hrp_version": "v4.1.0-beta" } \ No newline at end of file diff --git a/examples/demo-with-go-plugin/testcases/requests.yml b/examples/demo-with-go-plugin/testcases/requests.yml index 5ed53dd8..bc9aa108 100644 --- a/examples/demo-with-go-plugin/testcases/requests.yml +++ b/examples/demo-with-go-plugin/testcases/requests.yml @@ -5,7 +5,6 @@ config: foo2: config_bar2 expect_foo1: config_bar1 expect_foo2: config_bar2 - base_url: "https://postman-echo.com" verify: False export: ["foo3"] @@ -18,7 +17,7 @@ teststeps: sum_v: "${sum_two_int(1, 2)}" request: method: GET - url: /get + url: $base_url/get params: foo1: $foo1 foo2: $foo2 @@ -39,7 +38,7 @@ teststeps: foo3: "bar32" request: method: POST - url: /post + url: $base_url/post headers: User-Agent: funplugin/${get_version()} Content-Type: "text/plain" @@ -53,7 +52,7 @@ teststeps: foo2: bar23 request: method: POST - url: /post + url: $base_url/post headers: User-Agent: funplugin/${get_version()} Content-Type: "application/x-www-form-urlencoded" diff --git a/examples/demo-with-py-plugin/.env b/examples/demo-with-py-plugin/.env new file mode 100644 index 00000000..59ecc742 --- /dev/null +++ b/examples/demo-with-py-plugin/.env @@ -0,0 +1,3 @@ +base_url=https://postman-echo.com +USERNAME=debugtalk +PASSWORD=123456 \ No newline at end of file diff --git a/examples/demo-with-py-plugin/.gitignore b/examples/demo-with-py-plugin/.gitignore index 33401380..4c8cb60c 100644 --- a/examples/demo-with-py-plugin/.gitignore +++ b/examples/demo-with-py-plugin/.gitignore @@ -1,4 +1,3 @@ -.env reports/ *.so .vscode/ diff --git a/examples/demo-with-py-plugin/proj.json b/examples/demo-with-py-plugin/proj.json index fd0b8628..131f18a1 100644 --- a/examples/demo-with-py-plugin/proj.json +++ b/examples/demo-with-py-plugin/proj.json @@ -1,6 +1,6 @@ { "project_name": "demo-with-py-plugin", "project_path": "/Users/debugtalk/MyProjects/HttpRunner-dev/httprunner/examples/demo-with-py-plugin", - "create_time": "2022-05-24T11:10:15.544763+08:00", + "create_time": "2022-05-24T22:21:30.722372+08:00", "hrp_version": "v4.1.0-beta" } \ No newline at end of file diff --git a/examples/demo-with-py-plugin/testcases/requests.yml b/examples/demo-with-py-plugin/testcases/requests.yml index 5ed53dd8..bc9aa108 100644 --- a/examples/demo-with-py-plugin/testcases/requests.yml +++ b/examples/demo-with-py-plugin/testcases/requests.yml @@ -5,7 +5,6 @@ config: foo2: config_bar2 expect_foo1: config_bar1 expect_foo2: config_bar2 - base_url: "https://postman-echo.com" verify: False export: ["foo3"] @@ -18,7 +17,7 @@ teststeps: sum_v: "${sum_two_int(1, 2)}" request: method: GET - url: /get + url: $base_url/get params: foo1: $foo1 foo2: $foo2 @@ -39,7 +38,7 @@ teststeps: foo3: "bar32" request: method: POST - url: /post + url: $base_url/post headers: User-Agent: funplugin/${get_version()} Content-Type: "text/plain" @@ -53,7 +52,7 @@ teststeps: foo2: bar23 request: method: POST - url: /post + url: $base_url/post headers: User-Agent: funplugin/${get_version()} Content-Type: "application/x-www-form-urlencoded" diff --git a/examples/demo-without-plugin/.env b/examples/demo-without-plugin/.env new file mode 100644 index 00000000..59ecc742 --- /dev/null +++ b/examples/demo-without-plugin/.env @@ -0,0 +1,3 @@ +base_url=https://postman-echo.com +USERNAME=debugtalk +PASSWORD=123456 \ No newline at end of file diff --git a/examples/demo-without-plugin/.gitignore b/examples/demo-without-plugin/.gitignore index 33401380..4c8cb60c 100644 --- a/examples/demo-without-plugin/.gitignore +++ b/examples/demo-without-plugin/.gitignore @@ -1,4 +1,3 @@ -.env reports/ *.so .vscode/ diff --git a/examples/demo-without-plugin/proj.json b/examples/demo-without-plugin/proj.json index c6eef7d9..c6b74b20 100644 --- a/examples/demo-without-plugin/proj.json +++ b/examples/demo-without-plugin/proj.json @@ -1,6 +1,6 @@ { "project_name": "demo-without-plugin", "project_path": "/Users/debugtalk/MyProjects/HttpRunner-dev/httprunner/examples/demo-without-plugin", - "create_time": "2022-05-24T11:10:16.993462+08:00", + "create_time": "2022-05-24T22:21:31.676833+08:00", "hrp_version": "v4.1.0-beta" } \ No newline at end of file diff --git a/hrp/config.go b/hrp/config.go index 76912aa7..7439489d 100644 --- a/hrp/config.go +++ b/hrp/config.go @@ -20,10 +20,10 @@ func NewConfig(name string) *TConfig { type TConfig struct { Name string `json:"name" yaml:"name"` // required Verify bool `json:"verify,omitempty" yaml:"verify,omitempty"` - BaseURL string `json:"base_url,omitempty" yaml:"base_url,omitempty"` - Headers map[string]string `json:"headers,omitempty" yaml:"headers,omitempty"` - Env map[string]string `json:"env,omitempty" yaml:"env,omitempty"` - Variables map[string]interface{} `json:"variables,omitempty" yaml:"variables,omitempty"` + BaseURL string `json:"base_url,omitempty" yaml:"base_url,omitempty"` // deprecated in v4.1, moved to env + Headers map[string]string `json:"headers,omitempty" yaml:"headers,omitempty"` // public request headers + Env map[string]string `json:"env,omitempty" yaml:"env,omitempty"` // environment variables + Variables map[string]interface{} `json:"variables,omitempty" yaml:"variables,omitempty"` // global variables Parameters map[string]interface{} `json:"parameters,omitempty" yaml:"parameters,omitempty"` ParametersSetting *TParamsConfig `json:"parameters_setting,omitempty" yaml:"parameters_setting,omitempty"` ThinkTimeSetting *ThinkTimeConfig `json:"think_time,omitempty" yaml:"think_time,omitempty"` diff --git a/hrp/internal/builtin/utils.go b/hrp/internal/builtin/utils.go index 8bdc661d..170c39d0 100644 --- a/hrp/internal/builtin/utils.go +++ b/hrp/internal/builtin/utils.go @@ -314,8 +314,7 @@ func parseEnvContent(file []byte, obj interface{}) error { var kv []string if strings.Contains(line, "=") { kv = strings.SplitN(line, "=", 2) - } - if strings.Contains(line, ":") { + } else if strings.Contains(line, ":") { kv = strings.SplitN(line, ":", 2) } if len(kv) != 2 { diff --git a/hrp/internal/scaffold/templates/env b/hrp/internal/scaffold/templates/env index 9b5dc360..59ecc742 100644 --- a/hrp/internal/scaffold/templates/env +++ b/hrp/internal/scaffold/templates/env @@ -1,2 +1,3 @@ +base_url=https://postman-echo.com USERNAME=debugtalk PASSWORD=123456 \ No newline at end of file diff --git a/hrp/internal/scaffold/templates/gitignore b/hrp/internal/scaffold/templates/gitignore index 33401380..4c8cb60c 100644 --- a/hrp/internal/scaffold/templates/gitignore +++ b/hrp/internal/scaffold/templates/gitignore @@ -1,4 +1,3 @@ -.env reports/ *.so .vscode/ diff --git a/hrp/internal/scaffold/templates/testcases/demo_requests.yml b/hrp/internal/scaffold/templates/testcases/demo_requests.yml index 5ed53dd8..bc9aa108 100644 --- a/hrp/internal/scaffold/templates/testcases/demo_requests.yml +++ b/hrp/internal/scaffold/templates/testcases/demo_requests.yml @@ -5,7 +5,6 @@ config: foo2: config_bar2 expect_foo1: config_bar1 expect_foo2: config_bar2 - base_url: "https://postman-echo.com" verify: False export: ["foo3"] @@ -18,7 +17,7 @@ teststeps: sum_v: "${sum_two_int(1, 2)}" request: method: GET - url: /get + url: $base_url/get params: foo1: $foo1 foo2: $foo2 @@ -39,7 +38,7 @@ teststeps: foo3: "bar32" request: method: POST - url: /post + url: $base_url/post headers: User-Agent: funplugin/${get_version()} Content-Type: "text/plain" @@ -53,7 +52,7 @@ teststeps: foo2: bar23 request: method: POST - url: /post + url: $base_url/post headers: User-Agent: funplugin/${get_version()} Content-Type: "application/x-www-form-urlencoded" diff --git a/hrp/runner.go b/hrp/runner.go index 62a9a6b7..b824d855 100644 --- a/hrp/runner.go +++ b/hrp/runner.go @@ -287,6 +287,19 @@ func (r *testCaseRunner) parseConfig() error { } r.parsedConfig.BaseURL = convertString(parsedBaseURL) + // merge config environment variables with base_url + // priority: env base_url > base_url + r.parsedConfig.Env = cfg.Env + if value, ok := r.parsedConfig.Env["base_url"]; !ok || value == "" { + r.parsedConfig.Env["base_url"] = r.parsedConfig.BaseURL + } + + // merge config variables with environment variables + // priority: env > config variables + for k, v := range r.parsedConfig.Env { + r.parsedConfig.Variables[k] = v + } + // ensure correction of think time config r.parsedConfig.ThinkTimeSetting.checkThinkTime() diff --git a/hrp/step_request.go b/hrp/step_request.go index d4e17e99..39e15d74 100644 --- a/hrp/step_request.go +++ b/hrp/step_request.go @@ -145,7 +145,8 @@ func (r *requestBuilder) prepareUrlParams(stepVariables map[string]interface{}) log.Error().Err(err).Msg("parse request url failed") return err } - rawUrl := buildURL(r.config.BaseURL, convertString(requestUrl)) + baseURL := stepVariables["base_url"].(string) + rawUrl := buildURL(baseURL, convertString(requestUrl)) // prepare request params var queryParams url.Values diff --git a/hrp/testcase.go b/hrp/testcase.go index 0aa548ec..348524c8 100644 --- a/hrp/testcase.go +++ b/hrp/testcase.go @@ -83,11 +83,20 @@ func (path *TestCasePath) ToTestCase() (*TestCase, error) { // load .env file dotEnvPath := filepath.Join(projectRootDir, ".env") if builtin.IsFilePathExists(dotEnvPath) { - testCase.Config.Env = make(map[string]string) - err = builtin.LoadFile(dotEnvPath, testCase.Config.Env) + envVars := make(map[string]string) + err = builtin.LoadFile(dotEnvPath, envVars) if err != nil { return nil, errors.Wrap(err, "failed to load .env file") } + + // override testcase config env with variables loaded from .env file + // priority: .env file > testcase config env + if testCase.Config.Env == nil { + testCase.Config.Env = make(map[string]string) + } + for key, value := range envVars { + testCase.Config.Env[key] = value + } } for _, step := range tc.TestSteps { From 36fb3b22f1564871ea7ee8ffe7c57f288b87e5a0 Mon Sep 17 00:00:00 2001 From: debugtalk <mail@debugtalk.com> Date: Tue, 24 May 2022 22:26:11 +0800 Subject: [PATCH 056/109] update docs --- docs/cmd/hrp.md | 3 ++- docs/cmd/hrp_boom.md | 2 +- docs/cmd/hrp_convert.md | 2 +- docs/cmd/hrp_har2case.md | 2 +- docs/cmd/hrp_pytest.md | 2 +- docs/cmd/hrp_run.md | 2 +- docs/cmd/hrp_startproject.md | 2 +- docs/cmd/hrp_wiki.md | 19 +++++++++++++++++++ examples/demo-with-go-plugin/proj.json | 2 +- examples/demo-with-py-plugin/proj.json | 2 +- examples/demo-without-plugin/proj.json | 2 +- hrp/runner.go | 6 +++++- 12 files changed, 35 insertions(+), 11 deletions(-) create mode 100644 docs/cmd/hrp_wiki.md diff --git a/docs/cmd/hrp.md b/docs/cmd/hrp.md index 097cf3fb..f758fe81 100644 --- a/docs/cmd/hrp.md +++ b/docs/cmd/hrp.md @@ -35,5 +35,6 @@ Copyright 2017 debugtalk * [hrp pytest](hrp_pytest.md) - run API test with pytest * [hrp run](hrp_run.md) - run API test with go engine * [hrp startproject](hrp_startproject.md) - create a scaffold project +* [hrp wiki](hrp_wiki.md) - visit https://httprunner.com -###### Auto generated by spf13/cobra on 20-May-2022 +###### Auto generated by spf13/cobra on 24-May-2022 diff --git a/docs/cmd/hrp_boom.md b/docs/cmd/hrp_boom.md index 7277436c..56ed75da 100644 --- a/docs/cmd/hrp_boom.md +++ b/docs/cmd/hrp_boom.md @@ -42,4 +42,4 @@ hrp boom [flags] * [hrp](hrp.md) - Next-Generation API Testing Solution. -###### Auto generated by spf13/cobra on 20-May-2022 +###### Auto generated by spf13/cobra on 24-May-2022 diff --git a/docs/cmd/hrp_convert.md b/docs/cmd/hrp_convert.md index 3b55f035..bc83ba0f 100644 --- a/docs/cmd/hrp_convert.md +++ b/docs/cmd/hrp_convert.md @@ -18,4 +18,4 @@ hrp convert $path... [flags] * [hrp](hrp.md) - Next-Generation API Testing Solution. -###### Auto generated by spf13/cobra on 20-May-2022 +###### Auto generated by spf13/cobra on 24-May-2022 diff --git a/docs/cmd/hrp_har2case.md b/docs/cmd/hrp_har2case.md index 41e46787..141aa7ee 100644 --- a/docs/cmd/hrp_har2case.md +++ b/docs/cmd/hrp_har2case.md @@ -24,4 +24,4 @@ hrp har2case $har_path... [flags] * [hrp](hrp.md) - Next-Generation API Testing Solution. -###### Auto generated by spf13/cobra on 20-May-2022 +###### Auto generated by spf13/cobra on 24-May-2022 diff --git a/docs/cmd/hrp_pytest.md b/docs/cmd/hrp_pytest.md index 0bdbab3c..45f6210c 100644 --- a/docs/cmd/hrp_pytest.md +++ b/docs/cmd/hrp_pytest.md @@ -16,4 +16,4 @@ hrp pytest $path ... [flags] * [hrp](hrp.md) - Next-Generation API Testing Solution. -###### Auto generated by spf13/cobra on 20-May-2022 +###### Auto generated by spf13/cobra on 24-May-2022 diff --git a/docs/cmd/hrp_run.md b/docs/cmd/hrp_run.md index 8a1fc59b..727aed2d 100644 --- a/docs/cmd/hrp_run.md +++ b/docs/cmd/hrp_run.md @@ -35,4 +35,4 @@ hrp run $path... [flags] * [hrp](hrp.md) - Next-Generation API Testing Solution. -###### Auto generated by spf13/cobra on 20-May-2022 +###### Auto generated by spf13/cobra on 24-May-2022 diff --git a/docs/cmd/hrp_startproject.md b/docs/cmd/hrp_startproject.md index b669b71d..c1749e1f 100644 --- a/docs/cmd/hrp_startproject.md +++ b/docs/cmd/hrp_startproject.md @@ -20,4 +20,4 @@ hrp startproject $project_name [flags] * [hrp](hrp.md) - Next-Generation API Testing Solution. -###### Auto generated by spf13/cobra on 20-May-2022 +###### Auto generated by spf13/cobra on 24-May-2022 diff --git a/docs/cmd/hrp_wiki.md b/docs/cmd/hrp_wiki.md new file mode 100644 index 00000000..9b09a01d --- /dev/null +++ b/docs/cmd/hrp_wiki.md @@ -0,0 +1,19 @@ +## hrp wiki + +visit https://httprunner.com + +``` +hrp wiki [flags] +``` + +### Options + +``` + -h, --help help for wiki +``` + +### SEE ALSO + +* [hrp](hrp.md) - Next-Generation API Testing Solution. + +###### Auto generated by spf13/cobra on 24-May-2022 diff --git a/examples/demo-with-go-plugin/proj.json b/examples/demo-with-go-plugin/proj.json index 3abb9f72..cd7a16d6 100644 --- a/examples/demo-with-go-plugin/proj.json +++ b/examples/demo-with-go-plugin/proj.json @@ -1,6 +1,6 @@ { "project_name": "demo-with-go-plugin", "project_path": "/Users/debugtalk/MyProjects/HttpRunner-dev/httprunner/examples/demo-with-go-plugin", - "create_time": "2022-05-24T22:21:23.330967+08:00", + "create_time": "2022-05-24T22:23:19.739989+08:00", "hrp_version": "v4.1.0-beta" } \ No newline at end of file diff --git a/examples/demo-with-py-plugin/proj.json b/examples/demo-with-py-plugin/proj.json index 131f18a1..486a386c 100644 --- a/examples/demo-with-py-plugin/proj.json +++ b/examples/demo-with-py-plugin/proj.json @@ -1,6 +1,6 @@ { "project_name": "demo-with-py-plugin", "project_path": "/Users/debugtalk/MyProjects/HttpRunner-dev/httprunner/examples/demo-with-py-plugin", - "create_time": "2022-05-24T22:21:30.722372+08:00", + "create_time": "2022-05-24T22:23:27.199105+08:00", "hrp_version": "v4.1.0-beta" } \ No newline at end of file diff --git a/examples/demo-without-plugin/proj.json b/examples/demo-without-plugin/proj.json index c6b74b20..247a3762 100644 --- a/examples/demo-without-plugin/proj.json +++ b/examples/demo-without-plugin/proj.json @@ -1,6 +1,6 @@ { "project_name": "demo-without-plugin", "project_path": "/Users/debugtalk/MyProjects/HttpRunner-dev/httprunner/examples/demo-without-plugin", - "create_time": "2022-05-24T22:21:31.676833+08:00", + "create_time": "2022-05-24T22:23:27.955511+08:00", "hrp_version": "v4.1.0-beta" } \ No newline at end of file diff --git a/hrp/runner.go b/hrp/runner.go index b824d855..97aa1a45 100644 --- a/hrp/runner.go +++ b/hrp/runner.go @@ -289,7 +289,11 @@ func (r *testCaseRunner) parseConfig() error { // merge config environment variables with base_url // priority: env base_url > base_url - r.parsedConfig.Env = cfg.Env + if cfg.Env != nil { + r.parsedConfig.Env = cfg.Env + } else { + r.parsedConfig.Env = make(map[string]string) + } if value, ok := r.parsedConfig.Env["base_url"]; !ok || value == "" { r.parsedConfig.Env["base_url"] = r.parsedConfig.BaseURL } From 5504ebab2a0ff5f0c246c97dae6c40046c06bf6e Mon Sep 17 00:00:00 2001 From: buyuxiang <347586493@qq.com> Date: Wed, 25 May 2022 09:45:49 +0800 Subject: [PATCH 057/109] bugfix: postman empty body options --- hrp/cmd/har2case.go | 1 - hrp/internal/convert/converter_postman.go | 15 ++++++++++----- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/hrp/cmd/har2case.go b/hrp/cmd/har2case.go index 9d5f2f10..7d6f1994 100644 --- a/hrp/cmd/har2case.go +++ b/hrp/cmd/har2case.go @@ -29,7 +29,6 @@ var har2caseCmd = &cobra.Command{ } if flagCount > 1 { return errors.New("please specify at most one conversion flag") - } convert.Run(har2caseOutputType, har2caseOutputDir, har2caseProfilePath, args) return nil diff --git a/hrp/internal/convert/converter_postman.go b/hrp/internal/convert/converter_postman.go index d373c23a..bb746e87 100644 --- a/hrp/internal/convert/converter_postman.go +++ b/hrp/internal/convert/converter_postman.go @@ -427,14 +427,19 @@ func (s *stepFromPostman) makeRequestBody(item *TItem, steps []*hrp.TStep) error func (s *stepFromPostman) makeRequestBodyRaw(item *TItem) (err error) { defer func() { if p := recover(); p != nil { - err = fmt.Errorf("make request body raw failed: %v", p) + err = fmt.Errorf("make request body (raw) failed: %v", p) } }() - // extract language type + // extract language type, default languageType: text + languageType := "text" iOptions := item.Request.Body.Options - iLanguage := iOptions.(map[string]interface{})["raw"] - languageType := iLanguage.(map[string]interface{})["language"].(string) + if iOptions != nil { + iLanguage := iOptions.(map[string]interface{})["raw"] + if iLanguage != nil { + languageType = iLanguage.(map[string]interface{})["language"].(string) + } + } // make request body and indicate Content-Type rawBody := item.Request.Body.Raw @@ -442,7 +447,7 @@ func (s *stepFromPostman) makeRequestBodyRaw(item *TItem) (err error) { var iBody interface{} err = json.Unmarshal([]byte(rawBody), &iBody) if err != nil { - return errors.Wrap(err, "make request body raw failed") + return errors.Wrap(err, "make request body (raw -> json) failed") } s.Request.Body = iBody } else { From 780a7e24e6305397e8b53632fbaf13ca265233c7 Mon Sep 17 00:00:00 2001 From: debugtalk <mail@debugtalk.com> Date: Wed, 25 May 2022 11:17:19 +0800 Subject: [PATCH 058/109] fix: unittest --- docs/CHANGELOG.md | 2 +- docs/cmd/hrp.md | 2 +- docs/cmd/hrp_boom.md | 2 +- docs/cmd/hrp_convert.md | 2 +- docs/cmd/hrp_har2case.md | 2 +- docs/cmd/hrp_pytest.md | 2 +- docs/cmd/hrp_run.md | 2 +- docs/cmd/hrp_startproject.md | 2 +- docs/cmd/hrp_wiki.md | 2 +- examples/demo-with-go-plugin/proj.json | 2 +- examples/demo-with-py-plugin/proj.json | 2 +- examples/demo-without-plugin/proj.json | 2 +- hrp/config.go | 4 ++-- hrp/runner.go | 14 ++++++++------ hrp/testcase.go | 6 +++--- 15 files changed, 25 insertions(+), 23 deletions(-) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index fb603c12..2d00f359 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -1,6 +1,6 @@ # Release History -## v4.1.0 (2022-05-24) +## v4.1.0 (2022-05-25) - feat: add `wiki` sub-command to open httprunner website diff --git a/docs/cmd/hrp.md b/docs/cmd/hrp.md index f758fe81..5e74e839 100644 --- a/docs/cmd/hrp.md +++ b/docs/cmd/hrp.md @@ -37,4 +37,4 @@ Copyright 2017 debugtalk * [hrp startproject](hrp_startproject.md) - create a scaffold project * [hrp wiki](hrp_wiki.md) - visit https://httprunner.com -###### Auto generated by spf13/cobra on 24-May-2022 +###### Auto generated by spf13/cobra on 25-May-2022 diff --git a/docs/cmd/hrp_boom.md b/docs/cmd/hrp_boom.md index 56ed75da..f6eb3a55 100644 --- a/docs/cmd/hrp_boom.md +++ b/docs/cmd/hrp_boom.md @@ -42,4 +42,4 @@ hrp boom [flags] * [hrp](hrp.md) - Next-Generation API Testing Solution. -###### Auto generated by spf13/cobra on 24-May-2022 +###### Auto generated by spf13/cobra on 25-May-2022 diff --git a/docs/cmd/hrp_convert.md b/docs/cmd/hrp_convert.md index bc83ba0f..d778b691 100644 --- a/docs/cmd/hrp_convert.md +++ b/docs/cmd/hrp_convert.md @@ -18,4 +18,4 @@ hrp convert $path... [flags] * [hrp](hrp.md) - Next-Generation API Testing Solution. -###### Auto generated by spf13/cobra on 24-May-2022 +###### Auto generated by spf13/cobra on 25-May-2022 diff --git a/docs/cmd/hrp_har2case.md b/docs/cmd/hrp_har2case.md index 141aa7ee..a37bbf1e 100644 --- a/docs/cmd/hrp_har2case.md +++ b/docs/cmd/hrp_har2case.md @@ -24,4 +24,4 @@ hrp har2case $har_path... [flags] * [hrp](hrp.md) - Next-Generation API Testing Solution. -###### Auto generated by spf13/cobra on 24-May-2022 +###### Auto generated by spf13/cobra on 25-May-2022 diff --git a/docs/cmd/hrp_pytest.md b/docs/cmd/hrp_pytest.md index 45f6210c..30682a20 100644 --- a/docs/cmd/hrp_pytest.md +++ b/docs/cmd/hrp_pytest.md @@ -16,4 +16,4 @@ hrp pytest $path ... [flags] * [hrp](hrp.md) - Next-Generation API Testing Solution. -###### Auto generated by spf13/cobra on 24-May-2022 +###### Auto generated by spf13/cobra on 25-May-2022 diff --git a/docs/cmd/hrp_run.md b/docs/cmd/hrp_run.md index 727aed2d..46725b8a 100644 --- a/docs/cmd/hrp_run.md +++ b/docs/cmd/hrp_run.md @@ -35,4 +35,4 @@ hrp run $path... [flags] * [hrp](hrp.md) - Next-Generation API Testing Solution. -###### Auto generated by spf13/cobra on 24-May-2022 +###### Auto generated by spf13/cobra on 25-May-2022 diff --git a/docs/cmd/hrp_startproject.md b/docs/cmd/hrp_startproject.md index c1749e1f..eb0b6bb9 100644 --- a/docs/cmd/hrp_startproject.md +++ b/docs/cmd/hrp_startproject.md @@ -20,4 +20,4 @@ hrp startproject $project_name [flags] * [hrp](hrp.md) - Next-Generation API Testing Solution. -###### Auto generated by spf13/cobra on 24-May-2022 +###### Auto generated by spf13/cobra on 25-May-2022 diff --git a/docs/cmd/hrp_wiki.md b/docs/cmd/hrp_wiki.md index 9b09a01d..1219555f 100644 --- a/docs/cmd/hrp_wiki.md +++ b/docs/cmd/hrp_wiki.md @@ -16,4 +16,4 @@ hrp wiki [flags] * [hrp](hrp.md) - Next-Generation API Testing Solution. -###### Auto generated by spf13/cobra on 24-May-2022 +###### Auto generated by spf13/cobra on 25-May-2022 diff --git a/examples/demo-with-go-plugin/proj.json b/examples/demo-with-go-plugin/proj.json index cd7a16d6..4dea84bc 100644 --- a/examples/demo-with-go-plugin/proj.json +++ b/examples/demo-with-go-plugin/proj.json @@ -1,6 +1,6 @@ { "project_name": "demo-with-go-plugin", "project_path": "/Users/debugtalk/MyProjects/HttpRunner-dev/httprunner/examples/demo-with-go-plugin", - "create_time": "2022-05-24T22:23:19.739989+08:00", + "create_time": "2022-05-25T11:14:42.750876+08:00", "hrp_version": "v4.1.0-beta" } \ No newline at end of file diff --git a/examples/demo-with-py-plugin/proj.json b/examples/demo-with-py-plugin/proj.json index 486a386c..cca69211 100644 --- a/examples/demo-with-py-plugin/proj.json +++ b/examples/demo-with-py-plugin/proj.json @@ -1,6 +1,6 @@ { "project_name": "demo-with-py-plugin", "project_path": "/Users/debugtalk/MyProjects/HttpRunner-dev/httprunner/examples/demo-with-py-plugin", - "create_time": "2022-05-24T22:23:27.199105+08:00", + "create_time": "2022-05-25T11:14:52.333942+08:00", "hrp_version": "v4.1.0-beta" } \ No newline at end of file diff --git a/examples/demo-without-plugin/proj.json b/examples/demo-without-plugin/proj.json index 247a3762..3fabec85 100644 --- a/examples/demo-without-plugin/proj.json +++ b/examples/demo-without-plugin/proj.json @@ -1,6 +1,6 @@ { "project_name": "demo-without-plugin", "project_path": "/Users/debugtalk/MyProjects/HttpRunner-dev/httprunner/examples/demo-without-plugin", - "create_time": "2022-05-24T22:23:27.955511+08:00", + "create_time": "2022-05-25T11:14:53.862348+08:00", "hrp_version": "v4.1.0-beta" } \ No newline at end of file diff --git a/hrp/config.go b/hrp/config.go index 7439489d..404e51c9 100644 --- a/hrp/config.go +++ b/hrp/config.go @@ -10,7 +10,7 @@ import ( func NewConfig(name string) *TConfig { return &TConfig{ Name: name, - Env: make(map[string]string), + Environs: make(map[string]string), Variables: make(map[string]interface{}), } } @@ -22,7 +22,7 @@ type TConfig struct { Verify bool `json:"verify,omitempty" yaml:"verify,omitempty"` BaseURL string `json:"base_url,omitempty" yaml:"base_url,omitempty"` // deprecated in v4.1, moved to env Headers map[string]string `json:"headers,omitempty" yaml:"headers,omitempty"` // public request headers - Env map[string]string `json:"env,omitempty" yaml:"env,omitempty"` // environment variables + Environs map[string]string `json:"environs,omitempty" yaml:"environs,omitempty"` // environment variables Variables map[string]interface{} `json:"variables,omitempty" yaml:"variables,omitempty"` // global variables Parameters map[string]interface{} `json:"parameters,omitempty" yaml:"parameters,omitempty"` ParametersSetting *TParamsConfig `json:"parameters_setting,omitempty" yaml:"parameters_setting,omitempty"` diff --git a/hrp/runner.go b/hrp/runner.go index 97aa1a45..ca4c9db6 100644 --- a/hrp/runner.go +++ b/hrp/runner.go @@ -289,18 +289,20 @@ func (r *testCaseRunner) parseConfig() error { // merge config environment variables with base_url // priority: env base_url > base_url - if cfg.Env != nil { - r.parsedConfig.Env = cfg.Env + if cfg.Environs != nil { + r.parsedConfig.Environs = cfg.Environs } else { - r.parsedConfig.Env = make(map[string]string) + r.parsedConfig.Environs = make(map[string]string) } - if value, ok := r.parsedConfig.Env["base_url"]; !ok || value == "" { - r.parsedConfig.Env["base_url"] = r.parsedConfig.BaseURL + if value, ok := r.parsedConfig.Environs["base_url"]; !ok || value == "" { + if r.parsedConfig.BaseURL != "" { + r.parsedConfig.Environs["base_url"] = r.parsedConfig.BaseURL + } } // merge config variables with environment variables // priority: env > config variables - for k, v := range r.parsedConfig.Env { + for k, v := range r.parsedConfig.Environs { r.parsedConfig.Variables[k] = v } diff --git a/hrp/testcase.go b/hrp/testcase.go index 348524c8..f8f8d222 100644 --- a/hrp/testcase.go +++ b/hrp/testcase.go @@ -91,11 +91,11 @@ func (path *TestCasePath) ToTestCase() (*TestCase, error) { // override testcase config env with variables loaded from .env file // priority: .env file > testcase config env - if testCase.Config.Env == nil { - testCase.Config.Env = make(map[string]string) + if testCase.Config.Environs == nil { + testCase.Config.Environs = make(map[string]string) } for key, value := range envVars { - testCase.Config.Env[key] = value + testCase.Config.Environs[key] = value } } From ce61c44480d73835839c3c647eed881c3f1dc05e Mon Sep 17 00:00:00 2001 From: buyuxiang <347586493@qq.com> Date: Wed, 25 May 2022 15:44:21 +0800 Subject: [PATCH 059/109] change: hrp convert README.md --- hrp/internal/convert/README.md | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/hrp/internal/convert/README.md b/hrp/internal/convert/README.md index d31381be..da426350 100644 --- a/hrp/internal/convert/README.md +++ b/hrp/internal/convert/README.md @@ -23,13 +23,13 @@ Global Flags: -l, --log-level string set log level (default "INFO") ``` -`hrp convert` 指令用于将 HAR/Postman/JMeter/Swagger 等格式的外部脚本转化为 JSON/YAML/gotest/pytest 形态的测试用例,同时也支持测试用例各个形态之间的相互转化,输出的测试用例文件名格式为 `不带扩展名的原文件名称` + `_test` + `json/yaml/go/py` 后缀。 +`hrp convert` 指令用于将 HAR/Postman/JMeter/Swagger 文件或 curl/Apache ab 指令转化为 JSON/YAML/gotest/pytest 形态的测试用例,同时也支持测试用例各个形态之间的相互转化。 该指令所有选项的详细说明如下: -1. `--to-json / --to-yaml / --to-gotest / --to-pytest` 用于将输入的外部脚本转化为对应形态的测试用例,四个选项中最多只能指定一个,如果不指定则默认会将输入转化为 JSON 形态的测试用例 +1. `--to-json / --to-yaml / --to-gotest / --to-pytest` 用于将输入转化为对应形态的测试用例,四个选项中最多只能指定一个,如果不指定则默认会将输入转化为 JSON 形态的测试用例 2. `--output-dir` 后接测试用例的期望输出目录的路径,用于将转换生成的测试用例输出到对应的文件夹 -3. `--profile` 后接 profile 配置文件的路径,目前支持替换(不存在则会创建)或者覆盖输入的外部脚本/测试用例中的 `Headers` 和 `Cookies` 信息,profile 文件的后缀可以为 `json/yaml/yml`,下面给出两类 profile 配置文件的示例: +3. `--profile` 后接 profile 配置文件的路径,目前支持替换(不存在则会创建)或者覆盖输入的外部脚本/测试用例中的 `Headers` 和 `Cookies` 信息,profile 文件的后缀可以为 `json/yaml/yml`,下面给出两类 profile 配置文件的示例: - 根据 profile 替换指定的 `Headers` 和 `Cookies` 信息 ```yaml headers: @@ -48,9 +48,10 @@ cookies: ## 注意事项 -1. `hrp convert` 可以自动识别输入类型,因此不需要通过选项来手动制定输入类型,如遇到无法识别、不支持或转换失败的情况,则会输出错误日志并跳过,不会影响其他转换过程的正常进行 -2. 在 profile 文件中,指定 `override` 字段为 `false/true` 可以选择修改模式为替换/覆盖。需要注意的是,如果不指定该字段则 profile 的默认修改模式为替换模式 -3. 输入为 JSON/YAML 测试用例时,良好兼容 Golang/Python 双引擎的请求体、断言格式细微差异,输出的 JSON/YAML 则统一采用 Golang 引擎的风格 +1. 输出的测试用例文件名格式为 `Postman 工程文件名称(不带拓展名)` + `_test` + `.json/.yaml/.go/.py 后缀`,如果该文件已经存在则会进行覆盖 +2. `hrp convert` 可以自动识别输入类型,因此不需要通过选项来手动制定输入类型,如遇到无法识别、不支持或转换失败的情况,则会输出错误日志并跳过,不会影响其他转换过程的正常进行 +3. 在 profile 文件中,指定 `override` 字段为 `false/true` 可以选择修改模式为替换/覆盖。需要注意的是,如果不指定该字段则 profile 的默认修改模式为替换模式 +4. 输入为 JSON/YAML 测试用例时,良好兼容 Golang/Python 双引擎的请求体、断言格式细微差异,输出的 JSON/YAML 则统一采用 Golang 引擎的风格 ## 转换流程图 From 235673ced580c0427ad4c0fbeb089a38b4f2306f Mon Sep 17 00:00:00 2001 From: debugtalk <mail@debugtalk.com> Date: Thu, 26 May 2022 13:19:21 +0800 Subject: [PATCH 060/109] fix: upgrage gopkg.in/yaml.v3 --- go.mod | 2 +- go.sum | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/go.mod b/go.mod index 34901ad6..5f821352 100644 --- a/go.mod +++ b/go.mod @@ -22,7 +22,7 @@ require ( github.com/spf13/cobra v1.2.1 github.com/stretchr/testify v1.7.0 golang.org/x/net v0.0.0-20220225172249-27dd8689420f - gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b + gopkg.in/yaml.v3 v3.0.0 ) // replace github.com/httprunner/funplugin => ../funplugin diff --git a/go.sum b/go.sum index a3bc4a71..3326932a 100644 --- a/go.sum +++ b/go.sum @@ -854,8 +854,9 @@ gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20191120175047-4206685974f2/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0 h1:hjy8E9ON/egN1tAYqKb61G10WtihqetD4sz2H+8nIeA= +gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= From ecd61c815f5f7fb7e18f607cdf7b6abb07b4b883 Mon Sep 17 00:00:00 2001 From: buyuxiang <347586493@qq.com> Date: Thu, 26 May 2022 16:04:46 +0800 Subject: [PATCH 061/109] update: httprunner make v4 --- hrp/internal/convert/converter.go | 5 +- hrp/internal/convert/converter_har.go | 32 +-------- hrp/internal/convert/converter_json.go | 27 +------- hrp/internal/convert/converter_postman.go | 35 +--------- hrp/internal/convert/converter_yaml.go | 27 +------- hrp/step_api.go | 2 +- hrp/testcase.go | 83 ++--------------------- httprunner/compat.py | 32 ++++++--- httprunner/compat_test.py | 55 +++++++++++++-- httprunner/make.py | 16 ++--- 10 files changed, 95 insertions(+), 219 deletions(-) diff --git a/hrp/internal/convert/converter.go b/hrp/internal/convert/converter.go index 8733614c..63e07690 100644 --- a/hrp/internal/convert/converter.go +++ b/hrp/internal/convert/converter.go @@ -241,7 +241,6 @@ func (c *TCaseConverter) ToGoTest() (string, error) { type ICaseConverter interface { Struct() *TCaseConverter ToJSON() (string, error) - ToJSONTemp() (string, error) ToYAML() (string, error) ToGoTest() (string, error) ToPyTest() (string, error) @@ -345,8 +344,8 @@ func makeTestCaseFromJSONYAML(iCaseConverter ICaseConverter) (*hrp.TCase, error) } func convertToPyTest(iCaseConverter ICaseConverter) (string, error) { - // convert to temporary json testcase compatible with python engine style - jsonPath, err := iCaseConverter.ToJSONTemp() + // convert to temporary json testcase + jsonPath, err := iCaseConverter.ToJSON() inputType := iCaseConverter.Struct().InputType if err != nil { return "", errors.Wrapf(err, "(%s -> pytest step 1) failed to convert to temporary json testcase", inputType.String()) diff --git a/hrp/internal/convert/converter_har.go b/hrp/internal/convert/converter_har.go index 1ee513ae..d35a9031 100644 --- a/hrp/internal/convert/converter_har.go +++ b/hrp/internal/convert/converter_har.go @@ -384,19 +384,6 @@ func (c *ConverterHAR) ToJSON() (string, error) { return jsonPath, nil } -func (c *ConverterHAR) ToJSONTemp() (string, error) { - tCase, err := c.makeTestCaseTemp() - if err != nil { - return "", err - } - jsonPath := c.converter.genOutputPath(suffixJSON) - err = builtin.Dump2JSON(tCase, jsonPath) - if err != nil { - return "", err - } - return jsonPath, nil -} - func (c *ConverterHAR) ToYAML() (string, error) { tCase, err := c.makeTestCase() if err != nil { @@ -429,24 +416,7 @@ func (c *ConverterHAR) makeTestCase() (*hrp.TCase, error) { Config: c.prepareConfig(), TestSteps: teststeps, } - err = tCase.MakeCompat2GoEngine() - if err != nil { - return nil, err - } - return tCase, nil -} - -func (c *ConverterHAR) makeTestCaseTemp() (*hrp.TCase, error) { - teststeps, err := c.prepareTestSteps() - if err != nil { - return nil, err - } - - tCase := &hrp.TCase{ - Config: c.prepareConfig(), - TestSteps: teststeps, - } - err = tCase.MakeCompat2PyEngine() + err = tCase.MakeCompat() if err != nil { return nil, err } diff --git a/hrp/internal/convert/converter_json.go b/hrp/internal/convert/converter_json.go index 5aa0b69a..fc380142 100644 --- a/hrp/internal/convert/converter_json.go +++ b/hrp/internal/convert/converter_json.go @@ -37,19 +37,6 @@ func (c *ConverterJSON) ToJSON() (string, error) { return jsonPath, nil } -func (c *ConverterJSON) ToJSONTemp() (string, error) { - testCase, err := c.makeTestCaseTemp() - if err != nil { - return "", err - } - jsonPath := c.converter.genOutputPath(suffixJSON) - err = builtin.Dump2JSON(testCase, jsonPath) - if err != nil { - return "", err - } - return jsonPath, nil -} - func (c *ConverterJSON) ToYAML() (string, error) { testCase, err := c.makeTestCase() if err != nil { @@ -91,19 +78,7 @@ func (c *ConverterJSON) makeTestCase() (*hrp.TCase, error) { if err != nil { return nil, err } - err = tCase.MakeCompat2GoEngine() - if err != nil { - return nil, err - } - return tCase, nil -} - -func (c *ConverterJSON) makeTestCaseTemp() (*hrp.TCase, error) { - tCase, err := makeTestCaseFromJSONYAML(c) - if err != nil { - return nil, err - } - err = tCase.MakeCompat2PyEngine() + err = tCase.MakeCompat() if err != nil { return nil, err } diff --git a/hrp/internal/convert/converter_postman.go b/hrp/internal/convert/converter_postman.go index bb746e87..bfa9a19e 100644 --- a/hrp/internal/convert/converter_postman.go +++ b/hrp/internal/convert/converter_postman.go @@ -144,19 +144,6 @@ func (c *ConverterPostman) ToJSON() (string, error) { return jsonPath, nil } -func (c *ConverterPostman) ToJSONTemp() (string, error) { - testCase, err := c.makeTestCaseTemp() - if err != nil { - return "", err - } - jsonPath := c.converter.genOutputPath(suffixJSON) - err = builtin.Dump2JSON(testCase, jsonPath) - if err != nil { - return "", err - } - return jsonPath, nil -} - func (c *ConverterPostman) ToYAML() (string, error) { testCase, err := c.makeTestCase() if err != nil { @@ -192,27 +179,7 @@ func (c *ConverterPostman) makeTestCase() (*hrp.TCase, error) { Config: c.prepareConfig(casePostman), TestSteps: teststeps, } - err = tCase.MakeCompat2GoEngine() - if err != nil { - return nil, err - } - return tCase, nil -} - -func (c *ConverterPostman) makeTestCaseTemp() (*hrp.TCase, error) { - casePostman, err := c.load() - if err != nil { - return nil, err - } - teststeps, err := c.prepareTestSteps(casePostman) - if err != nil { - return nil, err - } - tCase := &hrp.TCase{ - Config: c.prepareConfig(casePostman), - TestSteps: teststeps, - } - err = tCase.MakeCompat2PyEngine() + err = tCase.MakeCompat() if err != nil { return nil, err } diff --git a/hrp/internal/convert/converter_yaml.go b/hrp/internal/convert/converter_yaml.go index 81d1b1a9..2ad783b1 100644 --- a/hrp/internal/convert/converter_yaml.go +++ b/hrp/internal/convert/converter_yaml.go @@ -34,19 +34,6 @@ func (c *ConverterYAML) ToJSON() (string, error) { return jsonPath, nil } -func (c *ConverterYAML) ToJSONTemp() (string, error) { - testCase, err := c.makeTestCaseTemp() - if err != nil { - return "", err - } - jsonPath := c.converter.genOutputPath(suffixJSON) - err = builtin.Dump2JSON(testCase, jsonPath) - if err != nil { - return "", err - } - return jsonPath, nil -} - func (c *ConverterYAML) ToYAML() (string, error) { testCase, err := c.makeTestCase() if err != nil { @@ -74,19 +61,7 @@ func (c *ConverterYAML) makeTestCase() (*hrp.TCase, error) { if err != nil { return nil, err } - err = tCase.MakeCompat2GoEngine() - if err != nil { - return nil, err - } - return tCase, nil -} - -func (c *ConverterYAML) makeTestCaseTemp() (*hrp.TCase, error) { - tCase, err := makeTestCaseFromJSONYAML(c) - if err != nil { - return nil, err - } - err = tCase.MakeCompat2PyEngine() + err = tCase.MakeCompat() if err != nil { return nil, err } diff --git a/hrp/step_api.go b/hrp/step_api.go index 7b57d896..1c9992ba 100644 --- a/hrp/step_api.go +++ b/hrp/step_api.go @@ -47,7 +47,7 @@ func (path *APIPath) ToAPI() (*API, error) { if err != nil { return nil, err } - err = convertValidatorCompat2GoEngine(api.Validators) + err = convertCompatValidator(api.Validators) convertExtract(api.Extract) return api, err } diff --git a/hrp/testcase.go b/hrp/testcase.go index 6548ab55..4182cec7 100644 --- a/hrp/testcase.go +++ b/hrp/testcase.go @@ -64,7 +64,7 @@ func (path *TestCasePath) ToTestCase() (*TestCase, error) { return nil, errors.New("incorrect testcase file format, expected config in file") } - err = tc.MakeCompat2GoEngine() + err = tc.MakeCompat() if err != nil { return nil, err } @@ -173,11 +173,11 @@ type TCase struct { TestSteps []*TStep `json:"teststeps" yaml:"teststeps"` } -// MakeCompat2GoEngine converts TCase compatible with Golang engine style -func (tc *TCase) MakeCompat2GoEngine() (err error) { +// MakeCompat converts TCase compatible with Golang engine style +func (tc *TCase) MakeCompat() (err error) { defer func() { if p := recover(); p != nil { - err = fmt.Errorf("[MakeCompat2GoEngine] convert compat testcase error: %v", p) + err = fmt.Errorf("[MakeCompat] convert compat testcase error: %v", p) } }() for _, step := range tc.TestSteps { @@ -194,7 +194,7 @@ func (tc *TCase) MakeCompat2GoEngine() (err error) { } // 2. deal with validators compatibility - err = convertValidatorCompat2GoEngine(step.Validators) + err = convertCompatValidator(step.Validators) if err != nil { return err } @@ -205,7 +205,7 @@ func (tc *TCase) MakeCompat2GoEngine() (err error) { return nil } -func convertValidatorCompat2GoEngine(Validators []interface{}) (err error) { +func convertCompatValidator(Validators []interface{}) (err error) { for i, iValidator := range Validators { if _, ok := iValidator.(Validator); ok { continue @@ -272,77 +272,6 @@ func convertCheckExpr(checkExpr string) string { return strings.Join(checkItems, ".") } -// MakeCompat2PyEngine converts TCase compatible with Python engine style -func (tc *TCase) MakeCompat2PyEngine() (err error) { - defer func() { - if p := recover(); p != nil { - err = fmt.Errorf("[MakeCompat2PyEngine] convert compat testcase error: %v", p) - } - }() - for _, step := range tc.TestSteps { - // 1. deal with request body compatibility - if step.Request != nil && step.Request.Body != nil { - if strings.HasPrefix(step.Request.Headers["Content-Type"], "application/json") { - step.Request.Json = step.Request.Body - step.Request.Body = nil - continue - } - step.Request.Data = step.Request.Body - step.Request.Body = nil - } - - // 2. deal with validators compatibility - err = convertValidatorCompat2PyEngine(step.Validators) - if err != nil { - return err - } - } - return -} - -func convertValidatorCompat2PyEngine(Validators []interface{}) (err error) { - for i, iValidator := range Validators { - if v, ok := iValidator.(Validator); ok { - var iValidatorContent []interface{} - iValidatorContent = append(iValidatorContent, v.Check) - iValidatorContent = append(iValidatorContent, v.Expect) - newValidatorMap := make(map[string]interface{}) - newValidatorMap[v.Assert] = iValidatorContent - Validators[i] = newValidatorMap - continue - } - validatorMap := iValidator.(map[string]interface{}) - // validator check priority: Python > Golang engine style - if len(validatorMap) == 1 { - // Python engine style - for _, iValidatorContent := range validatorMap { - validatorContent := iValidatorContent.([]interface{}) - if len(validatorContent) > 3 { - return fmt.Errorf("unexpected validator format: %v", validatorMap) - } - } - continue - } - iCheck, checkExisted := validatorMap["check"] - iAssert, assertExisted := validatorMap["assert"] - iExpect, expectExisted := validatorMap["expect"] - if checkExisted && assertExisted && expectExisted { - // Golang engine style - var validatorContent []interface{} - validatorContent = append(validatorContent, iCheck) - validatorContent = append(validatorContent, iExpect) - if iMsg, msgExisted := validatorMap["msg"]; msgExisted { - validatorContent = append(validatorContent, iMsg) - } - newValidatorMap := make(map[string]interface{}) - newValidatorMap[iAssert.(string)] = validatorContent - Validators[i] = newValidatorMap - continue - } - return fmt.Errorf("unexpected validator format: %v", validatorMap) - } - return -} func LoadTestCases(iTestCases ...ITestCase) ([]*TestCase, error) { testCases := make([]*TestCase, 0) diff --git a/httprunner/compat.py b/httprunner/compat.py index c7352f6a..4732783a 100644 --- a/httprunner/compat.py +++ b/httprunner/compat.py @@ -1,5 +1,5 @@ """ -This module handles compatibility issues between testcase format v2 and v3. +This module handles compatibility issues between testcase format v2, v3 and v4. """ import os import sys @@ -14,9 +14,8 @@ from httprunner.utils import sort_dict_by_custom_order def convert_variables( - raw_variables: Union[Dict, Text], test_path: Text + raw_variables: Union[Dict, Text], test_path: Text ) -> Dict[Text, Any]: - if isinstance(raw_variables, Dict): return raw_variables @@ -33,6 +32,18 @@ def convert_variables( ) +def _convert_request(request: Dict) -> Dict: + if "body" in request: + content_type = "" + if "headers" in request and "Content-Type" in request["headers"]: + content_type = request["headers"]["Content-Type"] + if content_type.startswith("application/json"): + request["json"] = request.pop("body") + else: + request["data"] = request.pop("body") + return _sort_request_by_custom_order(request) + + def _convert_jmespath(raw: Text) -> Text: if not isinstance(raw, Text): raise exceptions.TestCaseFormatError(f"Invalid jmespath extractor: {raw}") @@ -153,6 +164,9 @@ def _ensure_step_attachment(step: Dict) -> Dict: "name": step["name"], } + if "request" in step: + test_dict["request"] = _convert_request(step["request"]) + if "variables" in step: test_dict["variables"] = step["variables"] @@ -181,11 +195,11 @@ def _ensure_step_attachment(step: Dict) -> Dict: return test_dict -def ensure_testcase_v3_api(api_content: Dict) -> Dict: - logger.info("convert api in v2 to testcase format v3") +def ensure_testcase_v4_api(api_content: Dict) -> Dict: + logger.info("convert api in v2/v3 to testcase format v4") teststep = { - "request": _sort_request_by_custom_order(api_content["request"]), + "request": _convert_request(api_content["request"]), } teststep.update(_ensure_step_attachment(api_content)) @@ -202,8 +216,8 @@ def ensure_testcase_v3_api(api_content: Dict) -> Dict: } -def ensure_testcase_v3(test_content: Dict) -> Dict: - logger.info("ensure compatibility with testcase format v2") +def ensure_testcase_v4(test_content: Dict) -> Dict: + logger.info("ensure compatibility with testcase format v2/v3") v3_content = {"config": test_content["config"], "teststeps": []} @@ -221,7 +235,7 @@ def ensure_testcase_v3(test_content: Dict) -> Dict: teststep = {} if "request" in step: - teststep["request"] = _sort_request_by_custom_order(step.pop("request")) + pass elif "api" in step: teststep["testcase"] = step.pop("api") elif "testcase" in step: diff --git a/httprunner/compat_test.py b/httprunner/compat_test.py index 391133a1..a7877fb6 100644 --- a/httprunner/compat_test.py +++ b/httprunner/compat_test.py @@ -26,6 +26,53 @@ class TestCompat(unittest.TestCase): with self.assertRaises(exceptions.TestCaseFormatError): compat.convert_variables(None, "examples/data/a-b.c/1.yml") + def test_convert_request(self): + request_with_json_body = { + "method": "POST", + "url": "https://postman-echo.com/post", + "headers": { + "Content-Type": "application/json" + }, + "body": { + "k1": "v1", + "k2": "v2" + } + } + self.assertEqual( + compat._convert_request(request_with_json_body), + { + "method": "POST", + "url": "https://postman-echo.com/post", + "headers": { + "Content-Type": "application/json" + }, + "json": { + "k1": "v1", + "k2": "v2" + } + } + ) + + request_with_text_body = { + "method": "POST", + "url": "https://postman-echo.com/post", + "headers": { + "Content-Type": "text/plain" + }, + "body": "have a nice day" + } + self.assertEqual( + compat._convert_request(request_with_text_body), + { + "method": "POST", + "url": "https://postman-echo.com/post", + "headers": { + "Content-Type": "text/plain" + }, + "data": "have a nice day" + } + ) + def test_convert_jmespath(self): self.assertEqual(compat._convert_jmespath("content.abc"), "body.abc") self.assertEqual(compat._convert_jmespath("json.abc"), "body.abc") @@ -85,7 +132,7 @@ class TestCompat(unittest.TestCase): [{"eq": ["body[0].name", 201]}], ) - def test_ensure_testcase_v3_api(self): + def test_ensure_testcase_v4_api(self): api_content = { "name": "get with params", "request": { @@ -98,7 +145,7 @@ class TestCompat(unittest.TestCase): "validate": [{"eq": ["content.varB", 200]}, {"lt": ["json.0.varC", 0]}], } self.assertEqual( - compat.ensure_testcase_v3_api(api_content), + compat.ensure_testcase_v4_api(api_content), { "config": { "name": "get with params", @@ -126,7 +173,7 @@ class TestCompat(unittest.TestCase): }, ) - def test_ensure_testcase_v3(self): + def test_ensure_testcase_v4(self): testcase_content = { "config": {"name": "xxx", "base_url": "https://httpbin.org"}, "teststeps": [ @@ -150,7 +197,7 @@ class TestCompat(unittest.TestCase): ], } self.assertEqual( - compat.ensure_testcase_v3(testcase_content), + compat.ensure_testcase_v4(testcase_content), { "config": {"name": "xxx", "base_url": "https://httpbin.org"}, "teststeps": [ diff --git a/httprunner/make.py b/httprunner/make.py index 75a4e783..7fe473b3 100644 --- a/httprunner/make.py +++ b/httprunner/make.py @@ -11,8 +11,8 @@ from httprunner import __version__, exceptions from httprunner.compat import ( convert_variables, ensure_path_sep, - ensure_testcase_v3, - ensure_testcase_v3_api, + ensure_testcase_v4, + ensure_testcase_v4_api, ) from httprunner.loader import ( convert_relative_project_root_dir, @@ -332,8 +332,8 @@ def make_teststep_chain_style(teststep: Dict) -> Text: def make_testcase(testcase: Dict, dir_path: Text = None) -> Text: """convert valid testcase dict to pytest file path""" - # ensure compatibility with testcase format v2 - testcase = ensure_testcase_v3(testcase) + # ensure compatibility with testcase format v2/v3 + testcase = ensure_testcase_v4(testcase) # validate testcase format load_testcase(testcase) @@ -373,9 +373,9 @@ def make_testcase(testcase: Dict, dir_path: Text = None) -> Text: if not isinstance(test_content, Dict): raise exceptions.TestCaseFormatError(f"Invalid teststep: {teststep}") - # api in v2 format, convert to v3 testcase + # api in v2/v3 format, convert to v4 testcase if "request" in test_content and "name" in test_content: - test_content = ensure_testcase_v3_api(test_content) + test_content = ensure_testcase_v4_api(test_content) test_content.setdefault("config", {})["path"] = ref_testcase_path ref_testcase_python_abs_path = make_testcase(test_content) @@ -473,9 +473,9 @@ def __make(tests_path: Text): ) continue - # api in v2 format, convert to v3 testcase + # api in v2/v3 format, convert to v4 testcase if "request" in test_content and "name" in test_content: - test_content = ensure_testcase_v3_api(test_content) + test_content = ensure_testcase_v4_api(test_content) if "config" not in test_content: logger.warning( From 6cbec4f49589ae0114df0caa3748a260c2b29a56 Mon Sep 17 00:00:00 2001 From: xucong053 <xucong053@outlook.com> Date: Thu, 26 May 2022 18:25:49 +0800 Subject: [PATCH 062/109] fix: failed to generate a report in failfast mode #1315 --- hrp/runner.go | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/hrp/runner.go b/hrp/runner.go index ca4c9db6..640e7af3 100644 --- a/hrp/runner.go +++ b/hrp/runner.go @@ -179,12 +179,13 @@ func (r *HRPRunner) Run(testcases ...ITestCase) error { }() for it := sessionRunner.parametersIterator; it.HasNext(); { - if err = sessionRunner.Start(it.Next()); err != nil { - log.Error().Err(err).Msg("[Run] run testcase failed") - return err - } + err = sessionRunner.Start(it.Next()) caseSummary := sessionRunner.GetSummary() s.appendCaseSummary(caseSummary) + if err != nil { + log.Error().Err(err).Msg("[Run] run testcase failed") + break + } } } s.Time.Duration = time.Since(s.Time.StartAt).Seconds() From 1dbb71f556bba943fbd1c82d178eafd768b8e490 Mon Sep 17 00:00:00 2001 From: buyuxiang <347586493@qq.com> Date: Thu, 26 May 2022 18:33:37 +0800 Subject: [PATCH 063/109] fix: set ulimit error --- hrp/internal/boomer/ulimit.go | 1 + 1 file changed, 1 insertion(+) diff --git a/hrp/internal/boomer/ulimit.go b/hrp/internal/boomer/ulimit.go index b83585ea..504a534d 100644 --- a/hrp/internal/boomer/ulimit.go +++ b/hrp/internal/boomer/ulimit.go @@ -23,6 +23,7 @@ func SetUlimit(limit uint64) { } rLimit.Cur = limit + rLimit.Max = limit log.Info().Uint64("limit", rLimit.Cur).Msg("set current ulimit") err = syscall.Setrlimit(syscall.RLIMIT_NOFILE, &rLimit) if err != nil { From c59c859e737a39ec1e3b0a396e628d7d797e4718 Mon Sep 17 00:00:00 2001 From: xucong053 <xucong053@outlook.com> Date: Mon, 23 May 2022 21:50:45 +0800 Subject: [PATCH 064/109] feat: create empty project by using hrp startproject #1280 --- examples/demo-with-go-plugin/proj.json | 6 -- .../testcases/ref_testcase.yml | 15 +++- .../testcases/requests.json | 80 +++++++++---------- .../testcases/requests.yml | 50 +++++++++--- examples/demo-with-py-plugin/proj.json | 6 -- .../testcases/ref_testcase.yml | 15 +++- .../testcases/requests.json | 80 +++++++++---------- .../testcases/requests.yml | 50 +++++++++--- examples/demo-without-plugin/proj.json | 6 -- hrp/cmd/scaffold.go | 4 +- hrp/internal/scaffold/examples_test.go | 24 +++++- hrp/internal/scaffold/main.go | 61 ++++++++------ .../testcases/demo_empty_request.json | 25 ++++++ .../testcases/demo_empty_request.yml | 16 ++++ .../templates/testcases/demo_ref_testcase.yml | 15 +++- .../templates/testcases/demo_requests.json | 80 +++++++++---------- .../templates/testcases/demo_requests.yml | 50 +++++++++--- 17 files changed, 377 insertions(+), 206 deletions(-) delete mode 100644 examples/demo-with-go-plugin/proj.json delete mode 100644 examples/demo-with-py-plugin/proj.json delete mode 100644 examples/demo-without-plugin/proj.json create mode 100644 hrp/internal/scaffold/templates/testcases/demo_empty_request.json create mode 100644 hrp/internal/scaffold/templates/testcases/demo_empty_request.yml diff --git a/examples/demo-with-go-plugin/proj.json b/examples/demo-with-go-plugin/proj.json deleted file mode 100644 index 4dea84bc..00000000 --- a/examples/demo-with-go-plugin/proj.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "project_name": "demo-with-go-plugin", - "project_path": "/Users/debugtalk/MyProjects/HttpRunner-dev/httprunner/examples/demo-with-go-plugin", - "create_time": "2022-05-25T11:14:42.750876+08:00", - "hrp_version": "v4.1.0-beta" -} \ No newline at end of file diff --git a/examples/demo-with-go-plugin/testcases/ref_testcase.yml b/examples/demo-with-go-plugin/testcases/ref_testcase.yml index 6cf32323..b904706f 100644 --- a/examples/demo-with-go-plugin/testcases/ref_testcase.yml +++ b/examples/demo-with-go-plugin/testcases/ref_testcase.yml @@ -28,6 +28,15 @@ teststeps: Content-Type: "application/x-www-form-urlencoded" data: "foo1=$foo1&foo2=$foo3" validate: - - eq: ["status_code", 200] - - eq: ["body.form.foo1", "bar1"] - - eq: ["body.form.foo2", "bar21"] + - check: status_code + assert: equal + expect: 200 + msg: check status_code + - check: body.form.foo1 + assert: equal + expect: bar1 + msg: check body.form.foo1 + - check: body.form.foo2 + assert: equal + expect: bar21 + msg: check body.form.foo2 \ No newline at end of file diff --git a/examples/demo-with-go-plugin/testcases/requests.json b/examples/demo-with-go-plugin/testcases/requests.json index 9b4214d7..24d3d38d 100644 --- a/examples/demo-with-go-plugin/testcases/requests.json +++ b/examples/demo-with-go-plugin/testcases/requests.json @@ -38,28 +38,28 @@ }, "validate": [ { - "eq": [ - "status_code", - 200 - ] + "check": "status_code", + "assert": "equal", + "expect": 200, + "msg": "check status_code" }, { - "eq": [ - "body.args.foo1", - "debugtalk" - ] + "check": "body.args.foo1", + "assert": "equal", + "expect": "debugtalk", + "msg": "check body.args.foo1" }, { - "eq": [ - "body.args.sum_v", - "3" - ] + "check": "body.args.sum_v", + "assert": "equal", + "expect": "3", + "msg": "check body.args.sum_v" }, { - "eq": [ - "body.args.foo2", - "bar21" - ] + "check": "body.args.foo2", + "assert": "equal", + "expect": "bar21", + "msg": "check body.args.foo2" } ] }, @@ -80,16 +80,16 @@ }, "validate": [ { - "eq": [ - "status_code", - 200 - ] + "check": "status_code", + "assert": "equal", + "expect": 200, + "msg": "check status_code" }, { - "eq": [ - "body.data", - "This is expected to be sent back as part of response body: bar12-$expect_foo2-bar32." - ] + "check": "body.data", + "assert": "equal", + "expect": "This is expected to be sent back as part of response body: bar12-$expect_foo2-bar32.", + "msg": "check body.data" } ] }, @@ -109,28 +109,28 @@ }, "validate": [ { - "eq": [ - "status_code", - 200 - ] + "check": "status_code", + "assert": "equal", + "expect": 200, + "msg": "check status_code" }, { - "eq": [ - "body.form.foo1", - "$expect_foo1" - ] + "check": "body.form.foo1", + "assert": "equal", + "expect": "$expect_foo1", + "msg": "check body.form.foo1" }, { - "eq": [ - "body.form.foo2", - "bar23" - ] + "check": "body.form.foo2", + "assert": "equal", + "expect": "bar23", + "msg": "check body.form.foo2" }, { - "eq": [ - "body.form.foo3", - "bar21" - ] + "check": "body.form.foo3", + "assert": "equal", + "expect": "bar21", + "msg": "check body.form.foo3" } ] } diff --git a/examples/demo-with-go-plugin/testcases/requests.yml b/examples/demo-with-go-plugin/testcases/requests.yml index bc9aa108..a713830b 100644 --- a/examples/demo-with-go-plugin/testcases/requests.yml +++ b/examples/demo-with-go-plugin/testcases/requests.yml @@ -27,10 +27,22 @@ teststeps: extract: foo3: "body.args.foo2" validate: - - eq: ["status_code", 200] - - eq: ["body.args.foo1", "debugtalk"] - - eq: ["body.args.sum_v", "3"] - - eq: ["body.args.foo2", "bar21"] + - check: status_code + assert: equal + expect: 200 + msg: check status_code + - check: body.args.foo1 + assert: equal + expect: debugtalk + msg: check body.args.foo1 + - check: body.args.sum_v + assert: equal + expect: "3" + msg: check body.args.sum_v + - check: body.args.foo2 + assert: equal + expect: bar21 + msg: check body.args.foo2 - name: post raw text variables: @@ -44,8 +56,14 @@ teststeps: Content-Type: "text/plain" data: "This is expected to be sent back as part of response body: $foo1-$foo2-$foo3." validate: - - eq: ["status_code", 200] - - eq: ["body.data", "This is expected to be sent back as part of response body: bar12-$expect_foo2-bar32."] + - check: status_code + assert: equal + expect: 200 + msg: check status_code + - check: body.data + assert: equal + expect: "This is expected to be sent back as part of response body: bar12-$expect_foo2-bar32." + msg: check body.data - name: post form data variables: @@ -58,7 +76,19 @@ teststeps: Content-Type: "application/x-www-form-urlencoded" data: "foo1=$foo1&foo2=$foo2&foo3=$foo3" validate: - - eq: ["status_code", 200] - - eq: ["body.form.foo1", "$expect_foo1"] - - eq: ["body.form.foo2", "bar23"] - - eq: ["body.form.foo3", "bar21"] + - check: status_code + assert: equal + expect: 200 + msg: check status_code + - check: body.form.foo1 + assert: equal + expect: $expect_foo1 + msg: check body.form.foo1 + - check: body.form.foo2 + assert: equal + expect: bar23 + msg: check body.form.foo2 + - check: body.form.foo3 + assert: equal + expect: bar21 + msg: check body.form.foo3 diff --git a/examples/demo-with-py-plugin/proj.json b/examples/demo-with-py-plugin/proj.json deleted file mode 100644 index cca69211..00000000 --- a/examples/demo-with-py-plugin/proj.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "project_name": "demo-with-py-plugin", - "project_path": "/Users/debugtalk/MyProjects/HttpRunner-dev/httprunner/examples/demo-with-py-plugin", - "create_time": "2022-05-25T11:14:52.333942+08:00", - "hrp_version": "v4.1.0-beta" -} \ No newline at end of file diff --git a/examples/demo-with-py-plugin/testcases/ref_testcase.yml b/examples/demo-with-py-plugin/testcases/ref_testcase.yml index 6cf32323..b904706f 100644 --- a/examples/demo-with-py-plugin/testcases/ref_testcase.yml +++ b/examples/demo-with-py-plugin/testcases/ref_testcase.yml @@ -28,6 +28,15 @@ teststeps: Content-Type: "application/x-www-form-urlencoded" data: "foo1=$foo1&foo2=$foo3" validate: - - eq: ["status_code", 200] - - eq: ["body.form.foo1", "bar1"] - - eq: ["body.form.foo2", "bar21"] + - check: status_code + assert: equal + expect: 200 + msg: check status_code + - check: body.form.foo1 + assert: equal + expect: bar1 + msg: check body.form.foo1 + - check: body.form.foo2 + assert: equal + expect: bar21 + msg: check body.form.foo2 \ No newline at end of file diff --git a/examples/demo-with-py-plugin/testcases/requests.json b/examples/demo-with-py-plugin/testcases/requests.json index 9b4214d7..24d3d38d 100644 --- a/examples/demo-with-py-plugin/testcases/requests.json +++ b/examples/demo-with-py-plugin/testcases/requests.json @@ -38,28 +38,28 @@ }, "validate": [ { - "eq": [ - "status_code", - 200 - ] + "check": "status_code", + "assert": "equal", + "expect": 200, + "msg": "check status_code" }, { - "eq": [ - "body.args.foo1", - "debugtalk" - ] + "check": "body.args.foo1", + "assert": "equal", + "expect": "debugtalk", + "msg": "check body.args.foo1" }, { - "eq": [ - "body.args.sum_v", - "3" - ] + "check": "body.args.sum_v", + "assert": "equal", + "expect": "3", + "msg": "check body.args.sum_v" }, { - "eq": [ - "body.args.foo2", - "bar21" - ] + "check": "body.args.foo2", + "assert": "equal", + "expect": "bar21", + "msg": "check body.args.foo2" } ] }, @@ -80,16 +80,16 @@ }, "validate": [ { - "eq": [ - "status_code", - 200 - ] + "check": "status_code", + "assert": "equal", + "expect": 200, + "msg": "check status_code" }, { - "eq": [ - "body.data", - "This is expected to be sent back as part of response body: bar12-$expect_foo2-bar32." - ] + "check": "body.data", + "assert": "equal", + "expect": "This is expected to be sent back as part of response body: bar12-$expect_foo2-bar32.", + "msg": "check body.data" } ] }, @@ -109,28 +109,28 @@ }, "validate": [ { - "eq": [ - "status_code", - 200 - ] + "check": "status_code", + "assert": "equal", + "expect": 200, + "msg": "check status_code" }, { - "eq": [ - "body.form.foo1", - "$expect_foo1" - ] + "check": "body.form.foo1", + "assert": "equal", + "expect": "$expect_foo1", + "msg": "check body.form.foo1" }, { - "eq": [ - "body.form.foo2", - "bar23" - ] + "check": "body.form.foo2", + "assert": "equal", + "expect": "bar23", + "msg": "check body.form.foo2" }, { - "eq": [ - "body.form.foo3", - "bar21" - ] + "check": "body.form.foo3", + "assert": "equal", + "expect": "bar21", + "msg": "check body.form.foo3" } ] } diff --git a/examples/demo-with-py-plugin/testcases/requests.yml b/examples/demo-with-py-plugin/testcases/requests.yml index bc9aa108..a713830b 100644 --- a/examples/demo-with-py-plugin/testcases/requests.yml +++ b/examples/demo-with-py-plugin/testcases/requests.yml @@ -27,10 +27,22 @@ teststeps: extract: foo3: "body.args.foo2" validate: - - eq: ["status_code", 200] - - eq: ["body.args.foo1", "debugtalk"] - - eq: ["body.args.sum_v", "3"] - - eq: ["body.args.foo2", "bar21"] + - check: status_code + assert: equal + expect: 200 + msg: check status_code + - check: body.args.foo1 + assert: equal + expect: debugtalk + msg: check body.args.foo1 + - check: body.args.sum_v + assert: equal + expect: "3" + msg: check body.args.sum_v + - check: body.args.foo2 + assert: equal + expect: bar21 + msg: check body.args.foo2 - name: post raw text variables: @@ -44,8 +56,14 @@ teststeps: Content-Type: "text/plain" data: "This is expected to be sent back as part of response body: $foo1-$foo2-$foo3." validate: - - eq: ["status_code", 200] - - eq: ["body.data", "This is expected to be sent back as part of response body: bar12-$expect_foo2-bar32."] + - check: status_code + assert: equal + expect: 200 + msg: check status_code + - check: body.data + assert: equal + expect: "This is expected to be sent back as part of response body: bar12-$expect_foo2-bar32." + msg: check body.data - name: post form data variables: @@ -58,7 +76,19 @@ teststeps: Content-Type: "application/x-www-form-urlencoded" data: "foo1=$foo1&foo2=$foo2&foo3=$foo3" validate: - - eq: ["status_code", 200] - - eq: ["body.form.foo1", "$expect_foo1"] - - eq: ["body.form.foo2", "bar23"] - - eq: ["body.form.foo3", "bar21"] + - check: status_code + assert: equal + expect: 200 + msg: check status_code + - check: body.form.foo1 + assert: equal + expect: $expect_foo1 + msg: check body.form.foo1 + - check: body.form.foo2 + assert: equal + expect: bar23 + msg: check body.form.foo2 + - check: body.form.foo3 + assert: equal + expect: bar21 + msg: check body.form.foo3 diff --git a/examples/demo-without-plugin/proj.json b/examples/demo-without-plugin/proj.json deleted file mode 100644 index 3fabec85..00000000 --- a/examples/demo-without-plugin/proj.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "project_name": "demo-without-plugin", - "project_path": "/Users/debugtalk/MyProjects/HttpRunner-dev/httprunner/examples/demo-without-plugin", - "create_time": "2022-05-25T11:14:53.862348+08:00", - "hrp_version": "v4.1.0-beta" -} \ No newline at end of file diff --git a/hrp/cmd/scaffold.go b/hrp/cmd/scaffold.go index 93a8e4b8..42b8accb 100644 --- a/hrp/cmd/scaffold.go +++ b/hrp/cmd/scaffold.go @@ -32,7 +32,7 @@ var scaffoldCmd = &cobra.Command{ pluginType = scaffold.Py // default } - err := scaffold.CreateScaffold(args[0], pluginType, force) + err := scaffold.CreateScaffold(args[0], pluginType, empty, force) if err != nil { log.Error().Err(err).Msg("create scaffold project failed") os.Exit(1) @@ -43,6 +43,7 @@ var scaffoldCmd = &cobra.Command{ } var ( + empty bool ignorePlugin bool genPythonPlugin bool genGoPlugin bool @@ -55,4 +56,5 @@ func init() { scaffoldCmd.Flags().BoolVar(&genPythonPlugin, "py", true, "generate hashicorp python plugin") scaffoldCmd.Flags().BoolVar(&genGoPlugin, "go", false, "generate hashicorp go plugin") scaffoldCmd.Flags().BoolVar(&ignorePlugin, "ignore-plugin", false, "ignore function plugin") + scaffoldCmd.Flags().BoolVar(&empty, "empty", false, "generate empty project") } diff --git a/hrp/internal/scaffold/examples_test.go b/hrp/internal/scaffold/examples_test.go index 3ec85cb5..dc992a99 100644 --- a/hrp/internal/scaffold/examples_test.go +++ b/hrp/internal/scaffold/examples_test.go @@ -6,19 +6,37 @@ import ( func TestGenDemoExamples(t *testing.T) { dir := "../../../examples/demo-with-go-plugin" - err := CreateScaffold(dir, Go, true) + err := CreateScaffold(dir, Go, false, true) if err != nil { t.Fatal() } dir = "../../../examples/demo-with-py-plugin" - err = CreateScaffold(dir, Py, true) + err = CreateScaffold(dir, Py, false, true) if err != nil { t.Fatal() } dir = "../../../examples/demo-without-plugin" - err = CreateScaffold(dir, Ignore, true) + err = CreateScaffold(dir, Ignore, false, true) + if err != nil { + t.Fatal() + } + + dir = "../../../examples/empty-demo-without-plugin" + err = CreateScaffold(dir, Ignore, true, true) + if err != nil { + t.Fatal() + } + + dir = "../../../examples/empty-demo-with-py-plugin" + err = CreateScaffold(dir, Py, true, true) + if err != nil { + t.Fatal() + } + + dir = "../../../examples/empty-demo-with-go-plugin" + err = CreateScaffold(dir, Go, true, true) if err != nil { t.Fatal() } diff --git a/hrp/internal/scaffold/main.go b/hrp/internal/scaffold/main.go index 3392d960..61ea346d 100644 --- a/hrp/internal/scaffold/main.go +++ b/hrp/internal/scaffold/main.go @@ -51,7 +51,7 @@ func CopyFile(templateFile, targetFile string) error { return nil } -func CreateScaffold(projectName string, pluginType PluginType, force bool) error { +func CreateScaffold(projectName string, pluginType PluginType, empty bool, force bool) error { // report event sdk.SendEvent(sdk.EventTracking{ Category: "Scaffold", @@ -127,38 +127,49 @@ func CreateScaffold(projectName string, pluginType PluginType, force bool) error return err } - // create demo testcases - if pluginType == Ignore { + // create project testcases + if empty { + // create empty project + err := CopyFile("templates/testcases/demo_empty_request.json", + filepath.Join(projectName, "testcases", "requests.json")) + if err != nil { + return err + } + } else if pluginType == Ignore { + // create project without funplugin err := CopyFile("templates/testcases/demo_without_funplugin.json", filepath.Join(projectName, "testcases", "requests.json")) if err != nil { return err } + } else { + // create project with funplugin + err = CopyFile("templates/testcases/demo_with_funplugin.json", + filepath.Join(projectName, "testcases", "demo.json")) + if err != nil { + return err + } + err = CopyFile("templates/testcases/demo_requests.json", + filepath.Join(projectName, "testcases", "requests.json")) + if err != nil { + return err + } + err = CopyFile("templates/testcases/demo_requests.yml", + filepath.Join(projectName, "testcases", "requests.yml")) + if err != nil { + return err + } + err = CopyFile("templates/testcases/demo_ref_testcase.yml", + filepath.Join(projectName, "testcases", "ref_testcase.yml")) + if err != nil { + return err + } + } + + if pluginType == Ignore { log.Info().Msg("skip creating function plugin") return nil } - - err = CopyFile("templates/testcases/demo_with_funplugin.json", - filepath.Join(projectName, "testcases", "demo.json")) - if err != nil { - return err - } - err = CopyFile("templates/testcases/demo_requests.json", - filepath.Join(projectName, "testcases", "requests.json")) - if err != nil { - return err - } - err = CopyFile("templates/testcases/demo_requests.yml", - filepath.Join(projectName, "testcases", "requests.yml")) - if err != nil { - return err - } - err = CopyFile("templates/testcases/demo_ref_testcase.yml", - filepath.Join(projectName, "testcases", "ref_testcase.yml")) - if err != nil { - return err - } - // create debugtalk function plugin switch pluginType { case Py: diff --git a/hrp/internal/scaffold/templates/testcases/demo_empty_request.json b/hrp/internal/scaffold/templates/testcases/demo_empty_request.json new file mode 100644 index 00000000..fc76e4aa --- /dev/null +++ b/hrp/internal/scaffold/templates/testcases/demo_empty_request.json @@ -0,0 +1,25 @@ +{ + "config": { + "name": "request methods testcase: empty testcase", + "variables": null, + "verify": false + }, + "teststeps": [ + { + "name": "", + "variables": null, + "request": { + "method": "GET", + "url": "https://" + }, + "validate": [ + { + "check": "status_code", + "assert": "equal", + "expect": 200, + "msg": "check status_code" + } + ] + } + ] +} \ No newline at end of file diff --git a/hrp/internal/scaffold/templates/testcases/demo_empty_request.yml b/hrp/internal/scaffold/templates/testcases/demo_empty_request.yml new file mode 100644 index 00000000..21586762 --- /dev/null +++ b/hrp/internal/scaffold/templates/testcases/demo_empty_request.yml @@ -0,0 +1,16 @@ +config: + name: "request methods testcase: empty testcase" + variables: + verify: False + +teststeps: + - name: + variables: + request: + method: GET + url: "https://" + validate: + - check: status_code + assert: equal + expect: 200 + msg: check status_code diff --git a/hrp/internal/scaffold/templates/testcases/demo_ref_testcase.yml b/hrp/internal/scaffold/templates/testcases/demo_ref_testcase.yml index 6cf32323..b904706f 100644 --- a/hrp/internal/scaffold/templates/testcases/demo_ref_testcase.yml +++ b/hrp/internal/scaffold/templates/testcases/demo_ref_testcase.yml @@ -28,6 +28,15 @@ teststeps: Content-Type: "application/x-www-form-urlencoded" data: "foo1=$foo1&foo2=$foo3" validate: - - eq: ["status_code", 200] - - eq: ["body.form.foo1", "bar1"] - - eq: ["body.form.foo2", "bar21"] + - check: status_code + assert: equal + expect: 200 + msg: check status_code + - check: body.form.foo1 + assert: equal + expect: bar1 + msg: check body.form.foo1 + - check: body.form.foo2 + assert: equal + expect: bar21 + msg: check body.form.foo2 \ No newline at end of file diff --git a/hrp/internal/scaffold/templates/testcases/demo_requests.json b/hrp/internal/scaffold/templates/testcases/demo_requests.json index 9b4214d7..24d3d38d 100644 --- a/hrp/internal/scaffold/templates/testcases/demo_requests.json +++ b/hrp/internal/scaffold/templates/testcases/demo_requests.json @@ -38,28 +38,28 @@ }, "validate": [ { - "eq": [ - "status_code", - 200 - ] + "check": "status_code", + "assert": "equal", + "expect": 200, + "msg": "check status_code" }, { - "eq": [ - "body.args.foo1", - "debugtalk" - ] + "check": "body.args.foo1", + "assert": "equal", + "expect": "debugtalk", + "msg": "check body.args.foo1" }, { - "eq": [ - "body.args.sum_v", - "3" - ] + "check": "body.args.sum_v", + "assert": "equal", + "expect": "3", + "msg": "check body.args.sum_v" }, { - "eq": [ - "body.args.foo2", - "bar21" - ] + "check": "body.args.foo2", + "assert": "equal", + "expect": "bar21", + "msg": "check body.args.foo2" } ] }, @@ -80,16 +80,16 @@ }, "validate": [ { - "eq": [ - "status_code", - 200 - ] + "check": "status_code", + "assert": "equal", + "expect": 200, + "msg": "check status_code" }, { - "eq": [ - "body.data", - "This is expected to be sent back as part of response body: bar12-$expect_foo2-bar32." - ] + "check": "body.data", + "assert": "equal", + "expect": "This is expected to be sent back as part of response body: bar12-$expect_foo2-bar32.", + "msg": "check body.data" } ] }, @@ -109,28 +109,28 @@ }, "validate": [ { - "eq": [ - "status_code", - 200 - ] + "check": "status_code", + "assert": "equal", + "expect": 200, + "msg": "check status_code" }, { - "eq": [ - "body.form.foo1", - "$expect_foo1" - ] + "check": "body.form.foo1", + "assert": "equal", + "expect": "$expect_foo1", + "msg": "check body.form.foo1" }, { - "eq": [ - "body.form.foo2", - "bar23" - ] + "check": "body.form.foo2", + "assert": "equal", + "expect": "bar23", + "msg": "check body.form.foo2" }, { - "eq": [ - "body.form.foo3", - "bar21" - ] + "check": "body.form.foo3", + "assert": "equal", + "expect": "bar21", + "msg": "check body.form.foo3" } ] } diff --git a/hrp/internal/scaffold/templates/testcases/demo_requests.yml b/hrp/internal/scaffold/templates/testcases/demo_requests.yml index bc9aa108..a713830b 100644 --- a/hrp/internal/scaffold/templates/testcases/demo_requests.yml +++ b/hrp/internal/scaffold/templates/testcases/demo_requests.yml @@ -27,10 +27,22 @@ teststeps: extract: foo3: "body.args.foo2" validate: - - eq: ["status_code", 200] - - eq: ["body.args.foo1", "debugtalk"] - - eq: ["body.args.sum_v", "3"] - - eq: ["body.args.foo2", "bar21"] + - check: status_code + assert: equal + expect: 200 + msg: check status_code + - check: body.args.foo1 + assert: equal + expect: debugtalk + msg: check body.args.foo1 + - check: body.args.sum_v + assert: equal + expect: "3" + msg: check body.args.sum_v + - check: body.args.foo2 + assert: equal + expect: bar21 + msg: check body.args.foo2 - name: post raw text variables: @@ -44,8 +56,14 @@ teststeps: Content-Type: "text/plain" data: "This is expected to be sent back as part of response body: $foo1-$foo2-$foo3." validate: - - eq: ["status_code", 200] - - eq: ["body.data", "This is expected to be sent back as part of response body: bar12-$expect_foo2-bar32."] + - check: status_code + assert: equal + expect: 200 + msg: check status_code + - check: body.data + assert: equal + expect: "This is expected to be sent back as part of response body: bar12-$expect_foo2-bar32." + msg: check body.data - name: post form data variables: @@ -58,7 +76,19 @@ teststeps: Content-Type: "application/x-www-form-urlencoded" data: "foo1=$foo1&foo2=$foo2&foo3=$foo3" validate: - - eq: ["status_code", 200] - - eq: ["body.form.foo1", "$expect_foo1"] - - eq: ["body.form.foo2", "bar23"] - - eq: ["body.form.foo3", "bar21"] + - check: status_code + assert: equal + expect: 200 + msg: check status_code + - check: body.form.foo1 + assert: equal + expect: $expect_foo1 + msg: check body.form.foo1 + - check: body.form.foo2 + assert: equal + expect: bar23 + msg: check body.form.foo2 + - check: body.form.foo3 + assert: equal + expect: bar21 + msg: check body.form.foo3 From f39a82755e45d15e2d4a104df6f6984bc7cf85d5 Mon Sep 17 00:00:00 2001 From: xucong053 <xucong053@outlook.com> Date: Thu, 26 May 2022 17:39:11 +0800 Subject: [PATCH 065/109] fix: failed to locate root dir even if proj.json exist --- hrp/plugin.go | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/hrp/plugin.go b/hrp/plugin.go index 67a86cfe..f9465a90 100644 --- a/hrp/plugin.go +++ b/hrp/plugin.go @@ -17,6 +17,7 @@ const ( goPluginFile = "debugtalk.so" // built from go plugin hashicorpGoPluginFile = "debugtalk.bin" // built from hashicorp go plugin hashicorpPyPluginFile = "debugtalk.py" // used for hashicorp python plugin + projectInfoFile = "proj.json" // used for ensuring root project ) func initPlugin(path string, logOn bool) (plugin funplugin.IPlugin, pluginDir string, err error) { @@ -118,9 +119,15 @@ func GetProjectRootDirPath(path string) (rootDir string, err error) { rootDir = filepath.Dir(pluginPath) return } + // fix: no debugtalk file in project but having proj.json created by startpeoject + projPath, err := locateFile(path, projectInfoFile) + if err == nil { + rootDir = filepath.Dir(projPath) + return + } // failed to locate project root dir - // maybe project plugin debugtalk.xx is not exist + // maybe project plugin debugtalk.xx and proj.json are not exist // use current dir instead return os.Getwd() } From a1f189b3f4fa54bc164be1149d3a3c74178ff3ce Mon Sep 17 00:00:00 2001 From: xucong053 <xucong053@outlook.com> Date: Thu, 26 May 2022 20:11:46 +0800 Subject: [PATCH 066/109] fix: unittest --- examples/demo-with-go-plugin/proj.json | 6 ++++ .../testcases/ref_testcase.yml | 2 +- .../testcases/requests.json | 4 +-- .../testcases/requests.yml | 4 +-- examples/demo-with-py-plugin/proj.json | 6 ++++ .../testcases/ref_testcase.yml | 2 +- .../testcases/requests.json | 4 +-- .../testcases/requests.yml | 4 +-- examples/demo-without-plugin/proj.json | 6 ++++ .../templates/testcases/demo_ref_api.json | 34 +++++++++---------- .../templates/testcases/demo_ref_testcase.yml | 2 +- .../templates/testcases/demo_requests.json | 4 +-- .../templates/testcases/demo_requests.yml | 4 +-- 13 files changed, 49 insertions(+), 33 deletions(-) create mode 100644 examples/demo-with-go-plugin/proj.json create mode 100644 examples/demo-with-py-plugin/proj.json create mode 100644 examples/demo-without-plugin/proj.json diff --git a/examples/demo-with-go-plugin/proj.json b/examples/demo-with-go-plugin/proj.json new file mode 100644 index 00000000..c845ecfa --- /dev/null +++ b/examples/demo-with-go-plugin/proj.json @@ -0,0 +1,6 @@ +{ + "project_name": "demo-with-go-plugin", + "project_path": "/Users/xxxxx/go/src/github.com/httprunner/httprunner/examples/demo-with-go-plugin", + "create_time": "2022-05-26T20:08:49.164545+08:00", + "hrp_version": "v4.1.0-beta" +} \ No newline at end of file diff --git a/examples/demo-with-go-plugin/testcases/ref_testcase.yml b/examples/demo-with-go-plugin/testcases/ref_testcase.yml index b904706f..e102b9ec 100644 --- a/examples/demo-with-go-plugin/testcases/ref_testcase.yml +++ b/examples/demo-with-go-plugin/testcases/ref_testcase.yml @@ -26,7 +26,7 @@ teststeps: headers: User-Agent: funplugin/${get_version()} Content-Type: "application/x-www-form-urlencoded" - data: "foo1=$foo1&foo2=$foo3" + body: "foo1=$foo1&foo2=$foo3" validate: - check: status_code assert: equal diff --git a/examples/demo-with-go-plugin/testcases/requests.json b/examples/demo-with-go-plugin/testcases/requests.json index 24d3d38d..162632b4 100644 --- a/examples/demo-with-go-plugin/testcases/requests.json +++ b/examples/demo-with-go-plugin/testcases/requests.json @@ -76,7 +76,7 @@ "User-Agent": "funplugin/${get_version()}", "Content-Type": "text/plain" }, - "data": "This is expected to be sent back as part of response body: $foo1-$foo2-$foo3." + "body": "This is expected to be sent back as part of response body: $foo1-$foo2-$foo3." }, "validate": [ { @@ -105,7 +105,7 @@ "User-Agent": "funplugin/${get_version()}", "Content-Type": "application/x-www-form-urlencoded" }, - "data": "foo1=$foo1&foo2=$foo2&foo3=$foo3" + "body": "foo1=$foo1&foo2=$foo2&foo3=$foo3" }, "validate": [ { diff --git a/examples/demo-with-go-plugin/testcases/requests.yml b/examples/demo-with-go-plugin/testcases/requests.yml index a713830b..988c970f 100644 --- a/examples/demo-with-go-plugin/testcases/requests.yml +++ b/examples/demo-with-go-plugin/testcases/requests.yml @@ -54,7 +54,7 @@ teststeps: headers: User-Agent: funplugin/${get_version()} Content-Type: "text/plain" - data: "This is expected to be sent back as part of response body: $foo1-$foo2-$foo3." + body: "This is expected to be sent back as part of response body: $foo1-$foo2-$foo3." validate: - check: status_code assert: equal @@ -74,7 +74,7 @@ teststeps: headers: User-Agent: funplugin/${get_version()} Content-Type: "application/x-www-form-urlencoded" - data: "foo1=$foo1&foo2=$foo2&foo3=$foo3" + body: "foo1=$foo1&foo2=$foo2&foo3=$foo3" validate: - check: status_code assert: equal diff --git a/examples/demo-with-py-plugin/proj.json b/examples/demo-with-py-plugin/proj.json new file mode 100644 index 00000000..e950c12c --- /dev/null +++ b/examples/demo-with-py-plugin/proj.json @@ -0,0 +1,6 @@ +{ + "project_name": "demo-with-py-plugin", + "project_path": "/Users/xxxxx/go/src/github.com/httprunner/httprunner/examples/demo-with-py-plugin", + "create_time": "2022-05-26T20:08:56.909632+08:00", + "hrp_version": "v4.1.0-beta" +} \ No newline at end of file diff --git a/examples/demo-with-py-plugin/testcases/ref_testcase.yml b/examples/demo-with-py-plugin/testcases/ref_testcase.yml index b904706f..e102b9ec 100644 --- a/examples/demo-with-py-plugin/testcases/ref_testcase.yml +++ b/examples/demo-with-py-plugin/testcases/ref_testcase.yml @@ -26,7 +26,7 @@ teststeps: headers: User-Agent: funplugin/${get_version()} Content-Type: "application/x-www-form-urlencoded" - data: "foo1=$foo1&foo2=$foo3" + body: "foo1=$foo1&foo2=$foo3" validate: - check: status_code assert: equal diff --git a/examples/demo-with-py-plugin/testcases/requests.json b/examples/demo-with-py-plugin/testcases/requests.json index 24d3d38d..162632b4 100644 --- a/examples/demo-with-py-plugin/testcases/requests.json +++ b/examples/demo-with-py-plugin/testcases/requests.json @@ -76,7 +76,7 @@ "User-Agent": "funplugin/${get_version()}", "Content-Type": "text/plain" }, - "data": "This is expected to be sent back as part of response body: $foo1-$foo2-$foo3." + "body": "This is expected to be sent back as part of response body: $foo1-$foo2-$foo3." }, "validate": [ { @@ -105,7 +105,7 @@ "User-Agent": "funplugin/${get_version()}", "Content-Type": "application/x-www-form-urlencoded" }, - "data": "foo1=$foo1&foo2=$foo2&foo3=$foo3" + "body": "foo1=$foo1&foo2=$foo2&foo3=$foo3" }, "validate": [ { diff --git a/examples/demo-with-py-plugin/testcases/requests.yml b/examples/demo-with-py-plugin/testcases/requests.yml index a713830b..988c970f 100644 --- a/examples/demo-with-py-plugin/testcases/requests.yml +++ b/examples/demo-with-py-plugin/testcases/requests.yml @@ -54,7 +54,7 @@ teststeps: headers: User-Agent: funplugin/${get_version()} Content-Type: "text/plain" - data: "This is expected to be sent back as part of response body: $foo1-$foo2-$foo3." + body: "This is expected to be sent back as part of response body: $foo1-$foo2-$foo3." validate: - check: status_code assert: equal @@ -74,7 +74,7 @@ teststeps: headers: User-Agent: funplugin/${get_version()} Content-Type: "application/x-www-form-urlencoded" - data: "foo1=$foo1&foo2=$foo2&foo3=$foo3" + body: "foo1=$foo1&foo2=$foo2&foo3=$foo3" validate: - check: status_code assert: equal diff --git a/examples/demo-without-plugin/proj.json b/examples/demo-without-plugin/proj.json new file mode 100644 index 00000000..80cc140d --- /dev/null +++ b/examples/demo-without-plugin/proj.json @@ -0,0 +1,6 @@ +{ + "project_name": "demo-without-plugin", + "project_path": "/Users/xxxxx/go/src/github.com/httprunner/httprunner/examples/demo-without-plugin", + "create_time": "2022-05-26T20:08:57.501166+08:00", + "hrp_version": "v4.1.0-beta" +} \ No newline at end of file diff --git a/hrp/internal/scaffold/templates/testcases/demo_ref_api.json b/hrp/internal/scaffold/templates/testcases/demo_ref_api.json index 8e69392f..7bc33c5e 100644 --- a/hrp/internal/scaffold/templates/testcases/demo_ref_api.json +++ b/hrp/internal/scaffold/templates/testcases/demo_ref_api.json @@ -8,16 +8,14 @@ "app_version": "2.8.6" }, "base_url": "https://postman-echo.com", - "herader": [ - { - "Accept": "*/*", - "Accept-Encoding": "gzip, deflate, br", - "Cache-Control": "no-cache", - "Connection": "keep-alive", - "Host": "postman-echo.com", - "User-Agent": "PostmanRuntime/7.28.4" - } - ], + "headers": { + "Accept": "*/*", + "Accept-Encoding": "gzip, deflate, br", + "Cache-Control": "no-cache", + "Connection": "keep-alive", + "Host": "postman-echo.com", + "User-Agent": "PostmanRuntime/7.28.4" + }, "verify": false, "export": [ "session_token" @@ -48,16 +46,16 @@ }, "validate": [ { - "eq": [ - "status_code", - 200 - ] + "check": "status_code", + "assert": "equal", + "expect": 200, + "msg": "check status_code" }, { - "eq": [ - "body.headers.postman-token", - "ea19464c-ddd4-4724-abe9-5e2b254c2723" - ] + "check": "body.headers.postman-token", + "assert": "equal", + "expect": "ea19464c-ddd4-4724-abe9-5e2b254c2723", + "msg": "check body.headers.postman-token" } ] }, diff --git a/hrp/internal/scaffold/templates/testcases/demo_ref_testcase.yml b/hrp/internal/scaffold/templates/testcases/demo_ref_testcase.yml index b904706f..e102b9ec 100644 --- a/hrp/internal/scaffold/templates/testcases/demo_ref_testcase.yml +++ b/hrp/internal/scaffold/templates/testcases/demo_ref_testcase.yml @@ -26,7 +26,7 @@ teststeps: headers: User-Agent: funplugin/${get_version()} Content-Type: "application/x-www-form-urlencoded" - data: "foo1=$foo1&foo2=$foo3" + body: "foo1=$foo1&foo2=$foo3" validate: - check: status_code assert: equal diff --git a/hrp/internal/scaffold/templates/testcases/demo_requests.json b/hrp/internal/scaffold/templates/testcases/demo_requests.json index 24d3d38d..162632b4 100644 --- a/hrp/internal/scaffold/templates/testcases/demo_requests.json +++ b/hrp/internal/scaffold/templates/testcases/demo_requests.json @@ -76,7 +76,7 @@ "User-Agent": "funplugin/${get_version()}", "Content-Type": "text/plain" }, - "data": "This is expected to be sent back as part of response body: $foo1-$foo2-$foo3." + "body": "This is expected to be sent back as part of response body: $foo1-$foo2-$foo3." }, "validate": [ { @@ -105,7 +105,7 @@ "User-Agent": "funplugin/${get_version()}", "Content-Type": "application/x-www-form-urlencoded" }, - "data": "foo1=$foo1&foo2=$foo2&foo3=$foo3" + "body": "foo1=$foo1&foo2=$foo2&foo3=$foo3" }, "validate": [ { diff --git a/hrp/internal/scaffold/templates/testcases/demo_requests.yml b/hrp/internal/scaffold/templates/testcases/demo_requests.yml index a713830b..988c970f 100644 --- a/hrp/internal/scaffold/templates/testcases/demo_requests.yml +++ b/hrp/internal/scaffold/templates/testcases/demo_requests.yml @@ -54,7 +54,7 @@ teststeps: headers: User-Agent: funplugin/${get_version()} Content-Type: "text/plain" - data: "This is expected to be sent back as part of response body: $foo1-$foo2-$foo3." + body: "This is expected to be sent back as part of response body: $foo1-$foo2-$foo3." validate: - check: status_code assert: equal @@ -74,7 +74,7 @@ teststeps: headers: User-Agent: funplugin/${get_version()} Content-Type: "application/x-www-form-urlencoded" - data: "foo1=$foo1&foo2=$foo2&foo3=$foo3" + body: "foo1=$foo1&foo2=$foo2&foo3=$foo3" validate: - check: status_code assert: equal From 30bc80e456b79cc3dc59a7b5afbdc8fd1f78c643 Mon Sep 17 00:00:00 2001 From: xucong053 <xucong053@outlook.com> Date: Thu, 26 May 2022 22:06:02 +0800 Subject: [PATCH 067/109] fix: modify logic of generating empty project --- docs/cmd/hrp.md | 2 +- docs/cmd/hrp_boom.md | 2 +- docs/cmd/hrp_convert.md | 2 +- docs/cmd/hrp_har2case.md | 4 +- docs/cmd/hrp_pytest.md | 2 +- docs/cmd/hrp_run.md | 2 +- docs/cmd/hrp_startproject.md | 3 +- docs/cmd/hrp_wiki.md | 2 +- examples/demo-with-go-plugin/proj.json | 2 +- examples/demo-with-py-plugin/proj.json | 2 +- examples/demo-without-plugin/proj.json | 2 +- hrp/cmd/scaffold.go | 6 ++- hrp/internal/scaffold/examples_test.go | 20 ++-------- hrp/internal/scaffold/main.go | 54 +++++++++++++------------- 14 files changed, 48 insertions(+), 57 deletions(-) diff --git a/docs/cmd/hrp.md b/docs/cmd/hrp.md index 74a6c8b2..a1b46ac0 100644 --- a/docs/cmd/hrp.md +++ b/docs/cmd/hrp.md @@ -37,4 +37,4 @@ Copyright 2017 debugtalk * [hrp startproject](hrp_startproject.md) - create a scaffold project * [hrp wiki](hrp_wiki.md) - visit https://httprunner.com -###### Auto generated by spf13/cobra on 25-May-2022 +###### Auto generated by spf13/cobra on 26-May-2022 diff --git a/docs/cmd/hrp_boom.md b/docs/cmd/hrp_boom.md index f6eb3a55..78dc8845 100644 --- a/docs/cmd/hrp_boom.md +++ b/docs/cmd/hrp_boom.md @@ -42,4 +42,4 @@ hrp boom [flags] * [hrp](hrp.md) - Next-Generation API Testing Solution. -###### Auto generated by spf13/cobra on 25-May-2022 +###### Auto generated by spf13/cobra on 26-May-2022 diff --git a/docs/cmd/hrp_convert.md b/docs/cmd/hrp_convert.md index c3b1b5cc..9d0bf745 100644 --- a/docs/cmd/hrp_convert.md +++ b/docs/cmd/hrp_convert.md @@ -22,4 +22,4 @@ hrp convert $path... [flags] * [hrp](hrp.md) - Next-Generation API Testing Solution. -###### Auto generated by spf13/cobra on 25-May-2022 +###### Auto generated by spf13/cobra on 26-May-2022 diff --git a/docs/cmd/hrp_har2case.md b/docs/cmd/hrp_har2case.md index a37bbf1e..8ca7631e 100644 --- a/docs/cmd/hrp_har2case.md +++ b/docs/cmd/hrp_har2case.md @@ -16,7 +16,7 @@ hrp har2case $har_path... [flags] -h, --help help for har2case -d, --output-dir string specify output directory, default to the same dir with har file -p, --profile string specify profile path to override headers and cookies - -j, --to-json convert to JSON format (default true) + -j, --to-json convert to JSON format (default) -y, --to-yaml convert to YAML format ``` @@ -24,4 +24,4 @@ hrp har2case $har_path... [flags] * [hrp](hrp.md) - Next-Generation API Testing Solution. -###### Auto generated by spf13/cobra on 25-May-2022 +###### Auto generated by spf13/cobra on 26-May-2022 diff --git a/docs/cmd/hrp_pytest.md b/docs/cmd/hrp_pytest.md index 30682a20..ebc5a25d 100644 --- a/docs/cmd/hrp_pytest.md +++ b/docs/cmd/hrp_pytest.md @@ -16,4 +16,4 @@ hrp pytest $path ... [flags] * [hrp](hrp.md) - Next-Generation API Testing Solution. -###### Auto generated by spf13/cobra on 25-May-2022 +###### Auto generated by spf13/cobra on 26-May-2022 diff --git a/docs/cmd/hrp_run.md b/docs/cmd/hrp_run.md index 46725b8a..2dd34349 100644 --- a/docs/cmd/hrp_run.md +++ b/docs/cmd/hrp_run.md @@ -35,4 +35,4 @@ hrp run $path... [flags] * [hrp](hrp.md) - Next-Generation API Testing Solution. -###### Auto generated by spf13/cobra on 25-May-2022 +###### Auto generated by spf13/cobra on 26-May-2022 diff --git a/docs/cmd/hrp_startproject.md b/docs/cmd/hrp_startproject.md index eb0b6bb9..00ab67ab 100644 --- a/docs/cmd/hrp_startproject.md +++ b/docs/cmd/hrp_startproject.md @@ -9,6 +9,7 @@ hrp startproject $project_name [flags] ### Options ``` + --empty generate empty project -f, --force force to overwrite existing project --go generate hashicorp go plugin -h, --help help for startproject @@ -20,4 +21,4 @@ hrp startproject $project_name [flags] * [hrp](hrp.md) - Next-Generation API Testing Solution. -###### Auto generated by spf13/cobra on 25-May-2022 +###### Auto generated by spf13/cobra on 26-May-2022 diff --git a/docs/cmd/hrp_wiki.md b/docs/cmd/hrp_wiki.md index 1219555f..932f1133 100644 --- a/docs/cmd/hrp_wiki.md +++ b/docs/cmd/hrp_wiki.md @@ -16,4 +16,4 @@ hrp wiki [flags] * [hrp](hrp.md) - Next-Generation API Testing Solution. -###### Auto generated by spf13/cobra on 25-May-2022 +###### Auto generated by spf13/cobra on 26-May-2022 diff --git a/examples/demo-with-go-plugin/proj.json b/examples/demo-with-go-plugin/proj.json index c845ecfa..1c051514 100644 --- a/examples/demo-with-go-plugin/proj.json +++ b/examples/demo-with-go-plugin/proj.json @@ -1,6 +1,6 @@ { "project_name": "demo-with-go-plugin", "project_path": "/Users/xxxxx/go/src/github.com/httprunner/httprunner/examples/demo-with-go-plugin", - "create_time": "2022-05-26T20:08:49.164545+08:00", + "create_time": "2022-05-26T22:08:10.455301+08:00", "hrp_version": "v4.1.0-beta" } \ No newline at end of file diff --git a/examples/demo-with-py-plugin/proj.json b/examples/demo-with-py-plugin/proj.json index e950c12c..a1f7d451 100644 --- a/examples/demo-with-py-plugin/proj.json +++ b/examples/demo-with-py-plugin/proj.json @@ -1,6 +1,6 @@ { "project_name": "demo-with-py-plugin", "project_path": "/Users/xxxxx/go/src/github.com/httprunner/httprunner/examples/demo-with-py-plugin", - "create_time": "2022-05-26T20:08:56.909632+08:00", + "create_time": "2022-05-26T22:08:18.580462+08:00", "hrp_version": "v4.1.0-beta" } \ No newline at end of file diff --git a/examples/demo-without-plugin/proj.json b/examples/demo-without-plugin/proj.json index 80cc140d..92230cbb 100644 --- a/examples/demo-without-plugin/proj.json +++ b/examples/demo-without-plugin/proj.json @@ -1,6 +1,6 @@ { "project_name": "demo-without-plugin", "project_path": "/Users/xxxxx/go/src/github.com/httprunner/httprunner/examples/demo-without-plugin", - "create_time": "2022-05-26T20:08:57.501166+08:00", + "create_time": "2022-05-26T22:08:19.331271+08:00", "hrp_version": "v4.1.0-beta" } \ No newline at end of file diff --git a/hrp/cmd/scaffold.go b/hrp/cmd/scaffold.go index 42b8accb..c51281ed 100644 --- a/hrp/cmd/scaffold.go +++ b/hrp/cmd/scaffold.go @@ -24,7 +24,9 @@ var scaffoldCmd = &cobra.Command{ } var pluginType scaffold.PluginType - if ignorePlugin { + if empty { + pluginType = scaffold.Empty + } else if ignorePlugin { pluginType = scaffold.Ignore } else if genGoPlugin { pluginType = scaffold.Go @@ -32,7 +34,7 @@ var scaffoldCmd = &cobra.Command{ pluginType = scaffold.Py // default } - err := scaffold.CreateScaffold(args[0], pluginType, empty, force) + err := scaffold.CreateScaffold(args[0], pluginType, force) if err != nil { log.Error().Err(err).Msg("create scaffold project failed") os.Exit(1) diff --git a/hrp/internal/scaffold/examples_test.go b/hrp/internal/scaffold/examples_test.go index dc992a99..3bde77b0 100644 --- a/hrp/internal/scaffold/examples_test.go +++ b/hrp/internal/scaffold/examples_test.go @@ -6,37 +6,25 @@ import ( func TestGenDemoExamples(t *testing.T) { dir := "../../../examples/demo-with-go-plugin" - err := CreateScaffold(dir, Go, false, true) + err := CreateScaffold(dir, Go, true) if err != nil { t.Fatal() } dir = "../../../examples/demo-with-py-plugin" - err = CreateScaffold(dir, Py, false, true) + err = CreateScaffold(dir, Py, true) if err != nil { t.Fatal() } dir = "../../../examples/demo-without-plugin" - err = CreateScaffold(dir, Ignore, false, true) + err = CreateScaffold(dir, Ignore, true) if err != nil { t.Fatal() } dir = "../../../examples/empty-demo-without-plugin" - err = CreateScaffold(dir, Ignore, true, true) - if err != nil { - t.Fatal() - } - - dir = "../../../examples/empty-demo-with-py-plugin" - err = CreateScaffold(dir, Py, true, true) - if err != nil { - t.Fatal() - } - - dir = "../../../examples/empty-demo-with-go-plugin" - err = CreateScaffold(dir, Go, true, true) + err = CreateScaffold(dir, Empty, true) if err != nil { t.Fatal() } diff --git a/hrp/internal/scaffold/main.go b/hrp/internal/scaffold/main.go index 61ea346d..0941838d 100644 --- a/hrp/internal/scaffold/main.go +++ b/hrp/internal/scaffold/main.go @@ -20,6 +20,7 @@ import ( type PluginType string const ( + Empty PluginType = "empty" Ignore PluginType = "ignore" Py PluginType = "py" Go PluginType = "go" @@ -51,7 +52,7 @@ func CopyFile(templateFile, targetFile string) error { return nil } -func CreateScaffold(projectName string, pluginType PluginType, empty bool, force bool) error { +func CreateScaffold(projectName string, pluginType PluginType, force bool) error { // report event sdk.SendEvent(sdk.EventTracking{ Category: "Scaffold", @@ -128,13 +129,14 @@ func CreateScaffold(projectName string, pluginType PluginType, empty bool, force } // create project testcases - if empty { + if pluginType == Empty { // create empty project err := CopyFile("templates/testcases/demo_empty_request.json", filepath.Join(projectName, "testcases", "requests.json")) if err != nil { return err } + return nil } else if pluginType == Ignore { // create project without funplugin err := CopyFile("templates/testcases/demo_without_funplugin.json", @@ -142,34 +144,32 @@ func CreateScaffold(projectName string, pluginType PluginType, empty bool, force if err != nil { return err } - } else { - // create project with funplugin - err = CopyFile("templates/testcases/demo_with_funplugin.json", - filepath.Join(projectName, "testcases", "demo.json")) - if err != nil { - return err - } - err = CopyFile("templates/testcases/demo_requests.json", - filepath.Join(projectName, "testcases", "requests.json")) - if err != nil { - return err - } - err = CopyFile("templates/testcases/demo_requests.yml", - filepath.Join(projectName, "testcases", "requests.yml")) - if err != nil { - return err - } - err = CopyFile("templates/testcases/demo_ref_testcase.yml", - filepath.Join(projectName, "testcases", "ref_testcase.yml")) - if err != nil { - return err - } - } - - if pluginType == Ignore { log.Info().Msg("skip creating function plugin") return nil } + + // create project with funplugin + err = CopyFile("templates/testcases/demo_with_funplugin.json", + filepath.Join(projectName, "testcases", "demo.json")) + if err != nil { + return err + } + err = CopyFile("templates/testcases/demo_requests.json", + filepath.Join(projectName, "testcases", "requests.json")) + if err != nil { + return err + } + err = CopyFile("templates/testcases/demo_requests.yml", + filepath.Join(projectName, "testcases", "requests.yml")) + if err != nil { + return err + } + err = CopyFile("templates/testcases/demo_ref_testcase.yml", + filepath.Join(projectName, "testcases", "ref_testcase.yml")) + if err != nil { + return err + } + // create debugtalk function plugin switch pluginType { case Py: From 83c554decb62af30b29e956f2167feb596cdfb32 Mon Sep 17 00:00:00 2001 From: xucong053 <xucong053@outlook.com> Date: Fri, 27 May 2022 11:29:41 +0800 Subject: [PATCH 068/109] update templates/testcases --- docs/cmd/hrp.md | 2 +- docs/cmd/hrp_boom.md | 2 +- docs/cmd/hrp_convert.md | 2 +- docs/cmd/hrp_har2case.md | 2 +- docs/cmd/hrp_pytest.md | 2 +- docs/cmd/hrp_run.md | 2 +- docs/cmd/hrp_startproject.md | 2 +- docs/cmd/hrp_wiki.md | 2 +- examples/demo-with-go-plugin/proj.json | 2 +- .../testcases/ref_testcase.yml | 15 ++---- .../testcases/requests.yml | 50 ++++--------------- examples/demo-with-py-plugin/proj.json | 2 +- .../testcases/ref_testcase.yml | 15 ++---- .../testcases/requests.yml | 50 ++++--------------- examples/demo-without-plugin/proj.json | 2 +- .../testcases/demo_empty_request.yml | 5 +- .../templates/testcases/demo_ref_testcase.yml | 15 ++---- .../templates/testcases/demo_requests.yml | 50 ++++--------------- 18 files changed, 51 insertions(+), 171 deletions(-) diff --git a/docs/cmd/hrp.md b/docs/cmd/hrp.md index a1b46ac0..c9499319 100644 --- a/docs/cmd/hrp.md +++ b/docs/cmd/hrp.md @@ -37,4 +37,4 @@ Copyright 2017 debugtalk * [hrp startproject](hrp_startproject.md) - create a scaffold project * [hrp wiki](hrp_wiki.md) - visit https://httprunner.com -###### Auto generated by spf13/cobra on 26-May-2022 +###### Auto generated by spf13/cobra on 27-May-2022 diff --git a/docs/cmd/hrp_boom.md b/docs/cmd/hrp_boom.md index 78dc8845..429a0ed3 100644 --- a/docs/cmd/hrp_boom.md +++ b/docs/cmd/hrp_boom.md @@ -42,4 +42,4 @@ hrp boom [flags] * [hrp](hrp.md) - Next-Generation API Testing Solution. -###### Auto generated by spf13/cobra on 26-May-2022 +###### Auto generated by spf13/cobra on 27-May-2022 diff --git a/docs/cmd/hrp_convert.md b/docs/cmd/hrp_convert.md index 9d0bf745..3083456c 100644 --- a/docs/cmd/hrp_convert.md +++ b/docs/cmd/hrp_convert.md @@ -22,4 +22,4 @@ hrp convert $path... [flags] * [hrp](hrp.md) - Next-Generation API Testing Solution. -###### Auto generated by spf13/cobra on 26-May-2022 +###### Auto generated by spf13/cobra on 27-May-2022 diff --git a/docs/cmd/hrp_har2case.md b/docs/cmd/hrp_har2case.md index 8ca7631e..592c5281 100644 --- a/docs/cmd/hrp_har2case.md +++ b/docs/cmd/hrp_har2case.md @@ -24,4 +24,4 @@ hrp har2case $har_path... [flags] * [hrp](hrp.md) - Next-Generation API Testing Solution. -###### Auto generated by spf13/cobra on 26-May-2022 +###### Auto generated by spf13/cobra on 27-May-2022 diff --git a/docs/cmd/hrp_pytest.md b/docs/cmd/hrp_pytest.md index ebc5a25d..711c8bac 100644 --- a/docs/cmd/hrp_pytest.md +++ b/docs/cmd/hrp_pytest.md @@ -16,4 +16,4 @@ hrp pytest $path ... [flags] * [hrp](hrp.md) - Next-Generation API Testing Solution. -###### Auto generated by spf13/cobra on 26-May-2022 +###### Auto generated by spf13/cobra on 27-May-2022 diff --git a/docs/cmd/hrp_run.md b/docs/cmd/hrp_run.md index 2dd34349..63da347e 100644 --- a/docs/cmd/hrp_run.md +++ b/docs/cmd/hrp_run.md @@ -35,4 +35,4 @@ hrp run $path... [flags] * [hrp](hrp.md) - Next-Generation API Testing Solution. -###### Auto generated by spf13/cobra on 26-May-2022 +###### Auto generated by spf13/cobra on 27-May-2022 diff --git a/docs/cmd/hrp_startproject.md b/docs/cmd/hrp_startproject.md index 00ab67ab..e55c5429 100644 --- a/docs/cmd/hrp_startproject.md +++ b/docs/cmd/hrp_startproject.md @@ -21,4 +21,4 @@ hrp startproject $project_name [flags] * [hrp](hrp.md) - Next-Generation API Testing Solution. -###### Auto generated by spf13/cobra on 26-May-2022 +###### Auto generated by spf13/cobra on 27-May-2022 diff --git a/docs/cmd/hrp_wiki.md b/docs/cmd/hrp_wiki.md index 932f1133..2eecbdd0 100644 --- a/docs/cmd/hrp_wiki.md +++ b/docs/cmd/hrp_wiki.md @@ -16,4 +16,4 @@ hrp wiki [flags] * [hrp](hrp.md) - Next-Generation API Testing Solution. -###### Auto generated by spf13/cobra on 26-May-2022 +###### Auto generated by spf13/cobra on 27-May-2022 diff --git a/examples/demo-with-go-plugin/proj.json b/examples/demo-with-go-plugin/proj.json index 1c051514..2b2fcb6b 100644 --- a/examples/demo-with-go-plugin/proj.json +++ b/examples/demo-with-go-plugin/proj.json @@ -1,6 +1,6 @@ { "project_name": "demo-with-go-plugin", "project_path": "/Users/xxxxx/go/src/github.com/httprunner/httprunner/examples/demo-with-go-plugin", - "create_time": "2022-05-26T22:08:10.455301+08:00", + "create_time": "2022-05-27T11:34:23.903959+08:00", "hrp_version": "v4.1.0-beta" } \ No newline at end of file diff --git a/examples/demo-with-go-plugin/testcases/ref_testcase.yml b/examples/demo-with-go-plugin/testcases/ref_testcase.yml index e102b9ec..0816481c 100644 --- a/examples/demo-with-go-plugin/testcases/ref_testcase.yml +++ b/examples/demo-with-go-plugin/testcases/ref_testcase.yml @@ -28,15 +28,6 @@ teststeps: Content-Type: "application/x-www-form-urlencoded" body: "foo1=$foo1&foo2=$foo3" validate: - - check: status_code - assert: equal - expect: 200 - msg: check status_code - - check: body.form.foo1 - assert: equal - expect: bar1 - msg: check body.form.foo1 - - check: body.form.foo2 - assert: equal - expect: bar21 - msg: check body.form.foo2 \ No newline at end of file + - eq: ["status_code", 200] + - eq: ["body.form.foo1", "bar1"] + - eq: ["body.form.foo2", "bar21"] \ No newline at end of file diff --git a/examples/demo-with-go-plugin/testcases/requests.yml b/examples/demo-with-go-plugin/testcases/requests.yml index 988c970f..034dbefb 100644 --- a/examples/demo-with-go-plugin/testcases/requests.yml +++ b/examples/demo-with-go-plugin/testcases/requests.yml @@ -27,22 +27,10 @@ teststeps: extract: foo3: "body.args.foo2" validate: - - check: status_code - assert: equal - expect: 200 - msg: check status_code - - check: body.args.foo1 - assert: equal - expect: debugtalk - msg: check body.args.foo1 - - check: body.args.sum_v - assert: equal - expect: "3" - msg: check body.args.sum_v - - check: body.args.foo2 - assert: equal - expect: bar21 - msg: check body.args.foo2 + - eq: ["status_code", 200] + - eq: ["body.args.foo1", "debugtalk"] + - eq: ["body.args.sum_v", "3"] + - eq: ["body.args.foo2", "bar21"] - name: post raw text variables: @@ -56,14 +44,8 @@ teststeps: Content-Type: "text/plain" body: "This is expected to be sent back as part of response body: $foo1-$foo2-$foo3." validate: - - check: status_code - assert: equal - expect: 200 - msg: check status_code - - check: body.data - assert: equal - expect: "This is expected to be sent back as part of response body: bar12-$expect_foo2-bar32." - msg: check body.data + - eq: ["status_code", 200] + - eq: ["body.data", "This is expected to be sent back as part of response body: bar12-$expect_foo2-bar32."] - name: post form data variables: @@ -76,19 +58,7 @@ teststeps: Content-Type: "application/x-www-form-urlencoded" body: "foo1=$foo1&foo2=$foo2&foo3=$foo3" validate: - - check: status_code - assert: equal - expect: 200 - msg: check status_code - - check: body.form.foo1 - assert: equal - expect: $expect_foo1 - msg: check body.form.foo1 - - check: body.form.foo2 - assert: equal - expect: bar23 - msg: check body.form.foo2 - - check: body.form.foo3 - assert: equal - expect: bar21 - msg: check body.form.foo3 + - eq: ["status_code", 200] + - eq: ["body.form.foo1", "$expect_foo1"] + - eq: ["body.form.foo2", "bar23"] + - eq: ["body.form.foo3", "bar21"] diff --git a/examples/demo-with-py-plugin/proj.json b/examples/demo-with-py-plugin/proj.json index a1f7d451..555bccd7 100644 --- a/examples/demo-with-py-plugin/proj.json +++ b/examples/demo-with-py-plugin/proj.json @@ -1,6 +1,6 @@ { "project_name": "demo-with-py-plugin", "project_path": "/Users/xxxxx/go/src/github.com/httprunner/httprunner/examples/demo-with-py-plugin", - "create_time": "2022-05-26T22:08:18.580462+08:00", + "create_time": "2022-05-27T11:34:31.852589+08:00", "hrp_version": "v4.1.0-beta" } \ No newline at end of file diff --git a/examples/demo-with-py-plugin/testcases/ref_testcase.yml b/examples/demo-with-py-plugin/testcases/ref_testcase.yml index e102b9ec..0816481c 100644 --- a/examples/demo-with-py-plugin/testcases/ref_testcase.yml +++ b/examples/demo-with-py-plugin/testcases/ref_testcase.yml @@ -28,15 +28,6 @@ teststeps: Content-Type: "application/x-www-form-urlencoded" body: "foo1=$foo1&foo2=$foo3" validate: - - check: status_code - assert: equal - expect: 200 - msg: check status_code - - check: body.form.foo1 - assert: equal - expect: bar1 - msg: check body.form.foo1 - - check: body.form.foo2 - assert: equal - expect: bar21 - msg: check body.form.foo2 \ No newline at end of file + - eq: ["status_code", 200] + - eq: ["body.form.foo1", "bar1"] + - eq: ["body.form.foo2", "bar21"] \ No newline at end of file diff --git a/examples/demo-with-py-plugin/testcases/requests.yml b/examples/demo-with-py-plugin/testcases/requests.yml index 988c970f..034dbefb 100644 --- a/examples/demo-with-py-plugin/testcases/requests.yml +++ b/examples/demo-with-py-plugin/testcases/requests.yml @@ -27,22 +27,10 @@ teststeps: extract: foo3: "body.args.foo2" validate: - - check: status_code - assert: equal - expect: 200 - msg: check status_code - - check: body.args.foo1 - assert: equal - expect: debugtalk - msg: check body.args.foo1 - - check: body.args.sum_v - assert: equal - expect: "3" - msg: check body.args.sum_v - - check: body.args.foo2 - assert: equal - expect: bar21 - msg: check body.args.foo2 + - eq: ["status_code", 200] + - eq: ["body.args.foo1", "debugtalk"] + - eq: ["body.args.sum_v", "3"] + - eq: ["body.args.foo2", "bar21"] - name: post raw text variables: @@ -56,14 +44,8 @@ teststeps: Content-Type: "text/plain" body: "This is expected to be sent back as part of response body: $foo1-$foo2-$foo3." validate: - - check: status_code - assert: equal - expect: 200 - msg: check status_code - - check: body.data - assert: equal - expect: "This is expected to be sent back as part of response body: bar12-$expect_foo2-bar32." - msg: check body.data + - eq: ["status_code", 200] + - eq: ["body.data", "This is expected to be sent back as part of response body: bar12-$expect_foo2-bar32."] - name: post form data variables: @@ -76,19 +58,7 @@ teststeps: Content-Type: "application/x-www-form-urlencoded" body: "foo1=$foo1&foo2=$foo2&foo3=$foo3" validate: - - check: status_code - assert: equal - expect: 200 - msg: check status_code - - check: body.form.foo1 - assert: equal - expect: $expect_foo1 - msg: check body.form.foo1 - - check: body.form.foo2 - assert: equal - expect: bar23 - msg: check body.form.foo2 - - check: body.form.foo3 - assert: equal - expect: bar21 - msg: check body.form.foo3 + - eq: ["status_code", 200] + - eq: ["body.form.foo1", "$expect_foo1"] + - eq: ["body.form.foo2", "bar23"] + - eq: ["body.form.foo3", "bar21"] diff --git a/examples/demo-without-plugin/proj.json b/examples/demo-without-plugin/proj.json index 92230cbb..72c78cbf 100644 --- a/examples/demo-without-plugin/proj.json +++ b/examples/demo-without-plugin/proj.json @@ -1,6 +1,6 @@ { "project_name": "demo-without-plugin", "project_path": "/Users/xxxxx/go/src/github.com/httprunner/httprunner/examples/demo-without-plugin", - "create_time": "2022-05-26T22:08:19.331271+08:00", + "create_time": "2022-05-27T11:34:32.548637+08:00", "hrp_version": "v4.1.0-beta" } \ No newline at end of file diff --git a/hrp/internal/scaffold/templates/testcases/demo_empty_request.yml b/hrp/internal/scaffold/templates/testcases/demo_empty_request.yml index 21586762..38e7c4a8 100644 --- a/hrp/internal/scaffold/templates/testcases/demo_empty_request.yml +++ b/hrp/internal/scaffold/templates/testcases/demo_empty_request.yml @@ -10,7 +10,4 @@ teststeps: method: GET url: "https://" validate: - - check: status_code - assert: equal - expect: 200 - msg: check status_code + - eq: ["status_code", 200] diff --git a/hrp/internal/scaffold/templates/testcases/demo_ref_testcase.yml b/hrp/internal/scaffold/templates/testcases/demo_ref_testcase.yml index e102b9ec..0816481c 100644 --- a/hrp/internal/scaffold/templates/testcases/demo_ref_testcase.yml +++ b/hrp/internal/scaffold/templates/testcases/demo_ref_testcase.yml @@ -28,15 +28,6 @@ teststeps: Content-Type: "application/x-www-form-urlencoded" body: "foo1=$foo1&foo2=$foo3" validate: - - check: status_code - assert: equal - expect: 200 - msg: check status_code - - check: body.form.foo1 - assert: equal - expect: bar1 - msg: check body.form.foo1 - - check: body.form.foo2 - assert: equal - expect: bar21 - msg: check body.form.foo2 \ No newline at end of file + - eq: ["status_code", 200] + - eq: ["body.form.foo1", "bar1"] + - eq: ["body.form.foo2", "bar21"] \ No newline at end of file diff --git a/hrp/internal/scaffold/templates/testcases/demo_requests.yml b/hrp/internal/scaffold/templates/testcases/demo_requests.yml index 988c970f..034dbefb 100644 --- a/hrp/internal/scaffold/templates/testcases/demo_requests.yml +++ b/hrp/internal/scaffold/templates/testcases/demo_requests.yml @@ -27,22 +27,10 @@ teststeps: extract: foo3: "body.args.foo2" validate: - - check: status_code - assert: equal - expect: 200 - msg: check status_code - - check: body.args.foo1 - assert: equal - expect: debugtalk - msg: check body.args.foo1 - - check: body.args.sum_v - assert: equal - expect: "3" - msg: check body.args.sum_v - - check: body.args.foo2 - assert: equal - expect: bar21 - msg: check body.args.foo2 + - eq: ["status_code", 200] + - eq: ["body.args.foo1", "debugtalk"] + - eq: ["body.args.sum_v", "3"] + - eq: ["body.args.foo2", "bar21"] - name: post raw text variables: @@ -56,14 +44,8 @@ teststeps: Content-Type: "text/plain" body: "This is expected to be sent back as part of response body: $foo1-$foo2-$foo3." validate: - - check: status_code - assert: equal - expect: 200 - msg: check status_code - - check: body.data - assert: equal - expect: "This is expected to be sent back as part of response body: bar12-$expect_foo2-bar32." - msg: check body.data + - eq: ["status_code", 200] + - eq: ["body.data", "This is expected to be sent back as part of response body: bar12-$expect_foo2-bar32."] - name: post form data variables: @@ -76,19 +58,7 @@ teststeps: Content-Type: "application/x-www-form-urlencoded" body: "foo1=$foo1&foo2=$foo2&foo3=$foo3" validate: - - check: status_code - assert: equal - expect: 200 - msg: check status_code - - check: body.form.foo1 - assert: equal - expect: $expect_foo1 - msg: check body.form.foo1 - - check: body.form.foo2 - assert: equal - expect: bar23 - msg: check body.form.foo2 - - check: body.form.foo3 - assert: equal - expect: bar21 - msg: check body.form.foo3 + - eq: ["status_code", 200] + - eq: ["body.form.foo1", "$expect_foo1"] + - eq: ["body.form.foo2", "bar23"] + - eq: ["body.form.foo3", "bar21"] From 67c0cb86409433c15327bd19fdb10eb323271fa9 Mon Sep 17 00:00:00 2001 From: xucong053 <xucong053@outlook.com> Date: Fri, 20 May 2022 16:53:09 +0800 Subject: [PATCH 069/109] feat: add build command for plugin --- hrp/cmd/build.go | 26 ++ .../build/examples/debugtalk_no_fungo.go | 40 +++ .../build/examples/debugtalk_no_funppy.py | 53 +++ hrp/internal/build/main.go | 304 ++++++++++++++++++ hrp/internal/build/main_test.go | 44 +++ .../build/templates/debugtalkGoTemplate | 17 + .../build/templates/debugtalkPythonTemplate | 15 + 7 files changed, 499 insertions(+) create mode 100644 hrp/cmd/build.go create mode 100644 hrp/internal/build/examples/debugtalk_no_fungo.go create mode 100644 hrp/internal/build/examples/debugtalk_no_funppy.py create mode 100644 hrp/internal/build/main.go create mode 100644 hrp/internal/build/main_test.go create mode 100644 hrp/internal/build/templates/debugtalkGoTemplate create mode 100644 hrp/internal/build/templates/debugtalkPythonTemplate diff --git a/hrp/cmd/build.go b/hrp/cmd/build.go new file mode 100644 index 00000000..093132ea --- /dev/null +++ b/hrp/cmd/build.go @@ -0,0 +1,26 @@ +package cmd + +import ( + "github.com/spf13/cobra" + + "github.com/httprunner/httprunner/v4/hrp/internal/build" +) + +var buildCmd = &cobra.Command{ + Use: "build $path ...", + Short: "build plugin for testing", + Long: `build python/go plugin for testing`, + Example: ` $ hrp build plugin/debugtalk.go + $ hrp build plugin/debugtalk.py`, + Args: cobra.MinimumNArgs(1), + PreRun: func(cmd *cobra.Command, args []string) { + setLogLevel(logLevel) + }, + RunE: func(cmd *cobra.Command, args []string) error { + return build.Run(args) + }, +} + +func init() { + rootCmd.AddCommand(buildCmd) +} diff --git a/hrp/internal/build/examples/debugtalk_no_fungo.go b/hrp/internal/build/examples/debugtalk_no_fungo.go new file mode 100644 index 00000000..9a57a294 --- /dev/null +++ b/hrp/internal/build/examples/debugtalk_no_fungo.go @@ -0,0 +1,40 @@ +package examples + +import ( + "fmt" +) + +func SumTwoInt(a, b int) int { + return a + b +} + +func SumInts(args ...int) int { + var sum int + for _, arg := range args { + sum += arg + } + return sum +} + +func Sum(args ...interface{}) (interface{}, error) { + var sum float64 + for _, arg := range args { + switch v := arg.(type) { + case int: + sum += float64(v) + case float64: + sum += v + default: + return nil, fmt.Errorf("unexpected type: %T", arg) + } + } + return sum, nil +} + +func SetupHookExample(args string) string { + return fmt.Sprintf("step name: %v, setup...", args) +} + +func TeardownHookExample(args string) string { + return fmt.Sprintf("step name: %v, teardown...", args) +} diff --git a/hrp/internal/build/examples/debugtalk_no_funppy.py b/hrp/internal/build/examples/debugtalk_no_funppy.py new file mode 100644 index 00000000..370206d6 --- /dev/null +++ b/hrp/internal/build/examples/debugtalk_no_funppy.py @@ -0,0 +1,53 @@ +import logging +import time +from typing import List + + +def sleep(n_secs): + time.sleep(n_secs) + + +def sum(*args): + result = 0 + for arg in args: + result += arg + return result + + +def sum_ints(*args: List[int]) -> int: + result = 0 + for arg in args: + result += arg + return result + + +def sum_two_int(a: int, b: int) -> int: + return a + b + + +def sum_two_string(a: str, b: str) -> str: + return a + b + + +def sum_strings(*args: List[str]) -> str: + result = "" + for arg in args: + result += arg + return result + + +def concatenate(*args: List[str]) -> str: + result = "" + for arg in args: + result += str(arg) + return result + + +def setup_hook_example(name): + logging.warning("setup_hook_example") + return f"setup_hook_example: {name}" + + +def teardown_hook_example(name): + logging.warning("teardown_hook_example") + return f"teardown_hook_example: {name}" diff --git a/hrp/internal/build/main.go b/hrp/internal/build/main.go new file mode 100644 index 00000000..747ed181 --- /dev/null +++ b/hrp/internal/build/main.go @@ -0,0 +1,304 @@ +package build + +import ( + "bufio" + _ "embed" + "fmt" + "io" + "io/ioutil" + "os" + "os/exec" + "path/filepath" + "regexp" + "strings" + "text/template" + + "github.com/httprunner/funplugin/shared" + "github.com/httprunner/httprunner/v4/hrp/internal/builtin" + "github.com/pkg/errors" + "github.com/rs/zerolog/log" +) + +const ( + funppy = `import funppy` + fungo = `"github.com/httprunner/funplugin/fungo"` + regexPythonFunctionName = `def ([a-zA-Z_]\w*)\(.*\)` + regexGoImport = `import\s*\(\n([\s\S]*)\n\)` + regexGoFunctionName = `func ([a-zA-Z_]\w*)\(.*\)` + regexGoFunctionContent = `func [\s\S]*?\n}\n` +) + +//go:embed templates/debugtalkPythonTemplate +var pyTemplate string + +//go:embed templates/debugtalkGoTemplate +var goTemplate string + +type TemplateContent struct { + Fun string // funplugin package + Regexps *Regexps // match import/function + Imports []string // python/go import + FromImports []string // python from...import... + Functions []string // python/go function + FunctionNames []string // function name set by user + FunctionSnakeNames []string // function snake name converts by function name for registering plugin +} + +type Regexps struct { + Import *regexp.Regexp + FunctionName *regexp.Regexp + FunctionContent *regexp.Regexp // including function define and body +} + +func (t *TemplateContent) parseGoContent(path string) error { + log.Info().Msg(fmt.Sprintf("start to parse %v", path)) + + content, err := os.ReadFile(path) + if err != nil { + log.Error().Err(err).Msg("failed to read file") + return err + } + originalContent := string(content) + + // parse imports + importSlice := t.Regexps.Import.FindAllStringSubmatch(originalContent, -1) + if len(importSlice) != 0 { + imports := strings.Replace(importSlice[0][1], "\t", "", -1) + for _, elem := range strings.Split(imports, "\n") { + t.Imports = append(t.Imports, elem) + } + } else { + if strings.Contains(originalContent, "\nimport ") { + return errors.New(`import style error, expected import ( ... )`) + } + } + // import fungo package + if !builtin.Contains(t.Imports, fungo) { + t.Imports = append(t.Imports, t.Fun) + } + + // parse function name + functionNameSlice := t.Regexps.FunctionName.FindAllStringSubmatch(originalContent, -1) + for _, elem := range functionNameSlice { + name := strings.Trim(elem[1], " ") + if name == "main" { + continue + } + t.FunctionNames = append(t.FunctionNames, name) + t.FunctionSnakeNames = append(t.FunctionSnakeNames, convertSnakeName(name)) + } + + // parse function content + functionContentSlice := t.Regexps.FunctionContent.FindAllStringSubmatch(originalContent, -1) + for _, f := range functionContentSlice { + if strings.Contains(f[0], "func main") { + continue + } + t.Functions = append(t.Functions, strings.Trim(f[0], "\n")) + } + return nil +} + +func (t *TemplateContent) parsePyContent(path string) error { + file, err := os.Open(path) + if err != nil { + fmt.Printf("Error: %s\n", err) + return err + } + defer file.Close() + + r := bufio.NewReader(file) + + // record content excluding import and main + content := "" + + // parse python content line by line + for { + l, _, err := r.ReadLine() + if err == io.EOF { + break + } + line := string(l) + + if strings.HasPrefix(line, "import") { + t.Imports = append(t.Imports, strings.Trim(line, " ")) + } else if strings.HasPrefix(line, "from") { + t.FromImports = append(t.FromImports, strings.Trim(line, " ")) + } else { + // no parse content at under of `if __name__ == "__main__"` + if strings.HasPrefix(line, "if __name__") { + break + } + if strings.HasPrefix(line, "def") { + functionNameSlice := t.Regexps.FunctionName.FindAllStringSubmatch(line, -1) + if len(functionNameSlice) == 0 { + continue + } + t.FunctionNames = append(t.FunctionNames, functionNameSlice[0][1]) + t.FunctionSnakeNames = append(t.FunctionSnakeNames, convertSnakeName(functionNameSlice[0][1])) + } + content += line + "\n" + } + } + // function content + t.Functions = append(t.Functions, strings.Trim(content, "\n")) + + // import funppy + if !builtin.Contains(t.Imports, t.Fun) { + t.Imports = append(t.Imports, t.Fun) + } + return nil +} + +func (t *TemplateContent) genDebugTalk(path string, templ string) error { + file, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE, 0o666) + if err != nil { + log.Error().Err(err).Msg("open file failed") + return err + } + defer file.Close() + writer := bufio.NewWriter(file) + tmpl := template.Must(template.New("debugtalk").Parse(templ)) + err = tmpl.Execute(writer, t) + if err != nil { + log.Error().Err(err).Msg("execute applies a parsed template to the specified data object failed") + return err + } + err = writer.Flush() + if err == nil { + log.Info().Str("path", path).Msg("generate debugtalk success") + } else { + log.Error().Str("path", path).Msg("generate debugtalk failed") + } + return err +} + +// buildGo builds debugtalk.go to debugtalk.bin +func buildGo(path string) error { + templateContent := &TemplateContent{ + Fun: fungo, + Regexps: &Regexps{ + Import: regexp.MustCompile(regexGoImport), + FunctionName: regexp.MustCompile(regexGoFunctionName), + FunctionContent: regexp.MustCompile(regexGoFunctionContent), + }, + } + dir, _ := filepath.Split(path) + + // create temp dir for building + tempDir, err := ioutil.TempDir("", "hrp_build") + if err != nil { + return err + } + + // check go sdk in tempDir + if err := builtin.ExecCommandInDir(exec.Command("go", "version"), tempDir); err != nil { + return errors.Wrap(err, "go sdk not installed") + } + + // create pluginDir + pluginDir := filepath.Join(tempDir, "plugin") + if err := builtin.CreateFolder(pluginDir); err != nil { + return err + } + // parse debugtalk.go in pluginDir + err = templateContent.parseGoContent(path) + if err != nil { + return err + } + // generate debugtalk.go in pluginDir + err = templateContent.genDebugTalk(filepath.Join(pluginDir, "debugtalk.go"), goTemplate) + if err != nil { + return err + } + + // create go mod + if err := builtin.ExecCommandInDir(exec.Command("go", "mod", "init", "plugin"), pluginDir); err != nil { + return err + } + + // download plugin dependency + // funplugin version should be locked + funplugin := fmt.Sprintf("github.com/httprunner/funplugin@%s", shared.Version) + if err := builtin.ExecCommandInDir(exec.Command("go", "get", funplugin), pluginDir); err != nil { + return err + } + + outputPath, err := filepath.Abs(filepath.Join(dir, "../debugtalk.bin")) + if err != nil { + return err + } + + // build plugin debugtalk.bin + if err := builtin.ExecCommandInDir(exec.Command("go", "build", "-o", outputPath, "debugtalk.go"), pluginDir); err != nil { + return err + } + log.Info().Msg(fmt.Sprintf("build %s to %s successfully", path, outputPath)) + return nil +} + +// buildPy completes funppy information in debugtalk.py +func buildPy(path string) error { + templateContent := &TemplateContent{ + Fun: funppy, + Regexps: &Regexps{ + FunctionName: regexp.MustCompile(regexPythonFunctionName), + }, + } + err := templateContent.parsePyContent(path) + if err != nil { + return err + } + + // generate debugtalk.py + dir, _ := filepath.Split(path) + err = templateContent.genDebugTalk(filepath.Join(dir, "../debugtalk.py"), pyTemplate) + if err != nil { + return err + } + + // ensure funppy in .env + _, err = builtin.EnsurePython3Venv("funppy") + if err != nil { + return err + } + + return nil +} + +func Run(args []string) (err error) { + for _, arg := range args { + ext := filepath.Ext(arg) + switch ext { + case ".py": + err = buildPy(arg) + case ".go": + err = buildGo(arg) + default: + return errors.New("type error, expected .py or .go") + } + if err != nil { + log.Error().Err(err).Msg(fmt.Sprintf("failed to build %s", arg)) + os.Exit(1) + } + } + return nil +} + +// convertSnakeName converts name to snake name +func convertSnakeName(originalName string) string { + snakeName := make([]byte, 0, len(originalName)*2) + flag := false + num := len(originalName) + for i := 0; i < num; i++ { + ch := originalName[i] + if i > 0 && ch >= 'A' && ch <= 'Z' && flag { + snakeName = append(snakeName, '_') + } + if ch != '_' { + flag = true + } + snakeName = append(snakeName, ch) + } + return strings.ToLower(string(snakeName)) +} diff --git a/hrp/internal/build/main_test.go b/hrp/internal/build/main_test.go new file mode 100644 index 00000000..ef9e769f --- /dev/null +++ b/hrp/internal/build/main_test.go @@ -0,0 +1,44 @@ +package build + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestRun(t *testing.T) { + err := Run([]string{"examples/debugtalk_no_funppy.py", "examples/debugtalk_no_fungo.go"}) + if !assert.Nil(t, err) { + t.Fatal() + } +} + +func TestConvertSnakeName(t *testing.T) { + testData := []struct { + expectedValue string + originalValue string + }{ + { + expectedValue: "test_name", + originalValue: "testName", + }, + { + expectedValue: "test", + originalValue: "test", + }, + { + expectedValue: "test_name", + originalValue: "TestName", + }, + { + expectedValue: "test_name", + originalValue: "test_name", + }, + } + for _, data := range testData { + name := convertSnakeName(data.originalValue) + if !assert.Equal(t, data.expectedValue, name) { + t.Fatal() + } + } +} diff --git a/hrp/internal/build/templates/debugtalkGoTemplate b/hrp/internal/build/templates/debugtalkGoTemplate new file mode 100644 index 00000000..805d0813 --- /dev/null +++ b/hrp/internal/build/templates/debugtalkGoTemplate @@ -0,0 +1,17 @@ +package main + +import ( +{{- range $import := .Imports }} + {{ $import -}} +{{ end }} +) + +{{ range $function := .Functions }} +{{ $function }} +{{ end }} +func main() { +{{- range $idx, $name := .FunctionNames }} + fungo.Register("{{ index $.FunctionSnakeNames $idx }}", {{ $name }}) +{{- end }} + fungo.Serve() +} diff --git a/hrp/internal/build/templates/debugtalkPythonTemplate b/hrp/internal/build/templates/debugtalkPythonTemplate new file mode 100644 index 00000000..d5b209ec --- /dev/null +++ b/hrp/internal/build/templates/debugtalkPythonTemplate @@ -0,0 +1,15 @@ +{{- range $import := .Imports }} +{{- $import}} +{{ end }} +{{ range $fromImport := .FromImports }} +{{- $fromImport}} +{{ end }} +{{ range $function := .Functions }} +{{ $function }} + +{{ end }} +if __name__ == "__main__": +{{- range $mainRegSnake := .FunctionSnakeNames }} + funppy.register("{{ $mainRegSnake }}", {{ $mainRegSnake }}) +{{- end }} + funppy.serve() From 2bee7ba3c7917f3ebacef0a367665e164ae99073 Mon Sep 17 00:00:00 2001 From: xucong053 <xucong053@outlook.com> Date: Thu, 26 May 2022 21:26:48 +0800 Subject: [PATCH 070/109] feat: add --output command in hrp build --- hrp/cmd/build.go | 8 +- .../build/examples/debugtalk_no_fungo.go | 6 ++ hrp/internal/build/main.go | 101 ++++++++---------- hrp/internal/build/main_test.go | 47 +++----- .../build/templates/debugtalkGoTemplate | 4 +- .../build/templates/debugtalkPythonTemplate | 4 +- 6 files changed, 79 insertions(+), 91 deletions(-) diff --git a/hrp/cmd/build.go b/hrp/cmd/build.go index 093132ea..da174bbc 100644 --- a/hrp/cmd/build.go +++ b/hrp/cmd/build.go @@ -12,15 +12,19 @@ var buildCmd = &cobra.Command{ Long: `build python/go plugin for testing`, Example: ` $ hrp build plugin/debugtalk.go $ hrp build plugin/debugtalk.py`, - Args: cobra.MinimumNArgs(1), + Args: cobra.ExactArgs(1), PreRun: func(cmd *cobra.Command, args []string) { setLogLevel(logLevel) }, RunE: func(cmd *cobra.Command, args []string) error { - return build.Run(args) + return build.Run(args[0], output) }, } +var output string + func init() { rootCmd.AddCommand(buildCmd) + + buildCmd.Flags().StringVarP(&output, "output", "o", "", "funplugin product output path, default: cwd") } diff --git a/hrp/internal/build/examples/debugtalk_no_fungo.go b/hrp/internal/build/examples/debugtalk_no_fungo.go index 9a57a294..12d5ce57 100644 --- a/hrp/internal/build/examples/debugtalk_no_fungo.go +++ b/hrp/internal/build/examples/debugtalk_no_fungo.go @@ -4,6 +4,8 @@ import ( "fmt" ) +import "os" + func SumTwoInt(a, b int) int { return a + b } @@ -38,3 +40,7 @@ func SetupHookExample(args string) string { func TeardownHookExample(args string) string { return fmt.Sprintf("step name: %v, teardown...", args) } + +func init() { + _, _ = os.Getwd() +} diff --git a/hrp/internal/build/main.go b/hrp/internal/build/main.go index 747ed181..afadf8aa 100644 --- a/hrp/internal/build/main.go +++ b/hrp/internal/build/main.go @@ -23,8 +23,9 @@ const ( funppy = `import funppy` fungo = `"github.com/httprunner/funplugin/fungo"` regexPythonFunctionName = `def ([a-zA-Z_]\w*)\(.*\)` - regexGoImport = `import\s*\(\n([\s\S]*)\n\)` - regexGoFunctionName = `func ([a-zA-Z_]\w*)\(.*\)` + regexGoImports = `import\s*\(\n([\s\S]*)\n\)` + regexGoImport = `import\s*(\"[\s\S]*\")\n` + regexGoFunctionName = `func ([A-Z][a-zA-Z_]\w*)\(.*\)` regexGoFunctionContent = `func [\s\S]*?\n}\n` ) @@ -35,17 +36,17 @@ var pyTemplate string var goTemplate string type TemplateContent struct { - Fun string // funplugin package - Regexps *Regexps // match import/function - Imports []string // python/go import - FromImports []string // python from...import... - Functions []string // python/go function - FunctionNames []string // function name set by user - FunctionSnakeNames []string // function snake name converts by function name for registering plugin + Fun string // funplugin package + Regexps *Regexps // match import/function + Imports []string // python/go import + FromImports []string // python from...import... + Functions []string // python/go function + FunctionNames []string // function name set by user } type Regexps struct { Import *regexp.Regexp + Imports *regexp.Regexp FunctionName *regexp.Regexp FunctionContent *regexp.Regexp // including function define and body } @@ -65,11 +66,14 @@ func (t *TemplateContent) parseGoContent(path string) error { if len(importSlice) != 0 { imports := strings.Replace(importSlice[0][1], "\t", "", -1) for _, elem := range strings.Split(imports, "\n") { - t.Imports = append(t.Imports, elem) + t.Imports = append(t.Imports, strings.TrimSpace(elem)) } - } else { - if strings.Contains(originalContent, "\nimport ") { - return errors.New(`import style error, expected import ( ... )`) + } + // parse import + importSlice = t.Regexps.Imports.FindAllStringSubmatch(originalContent, -1) + if len(importSlice) != 0 { + for _, elem := range importSlice { + t.Imports = append(t.Imports, strings.TrimSpace(elem[1])) } } // import fungo package @@ -85,7 +89,6 @@ func (t *TemplateContent) parseGoContent(path string) error { continue } t.FunctionNames = append(t.FunctionNames, name) - t.FunctionSnakeNames = append(t.FunctionSnakeNames, convertSnakeName(name)) } // parse function content @@ -135,7 +138,6 @@ func (t *TemplateContent) parsePyContent(path string) error { continue } t.FunctionNames = append(t.FunctionNames, functionNameSlice[0][1]) - t.FunctionSnakeNames = append(t.FunctionSnakeNames, convertSnakeName(functionNameSlice[0][1])) } content += line + "\n" } @@ -174,16 +176,16 @@ func (t *TemplateContent) genDebugTalk(path string, templ string) error { } // buildGo builds debugtalk.go to debugtalk.bin -func buildGo(path string) error { +func buildGo(path string, output string) error { templateContent := &TemplateContent{ Fun: fungo, Regexps: &Regexps{ Import: regexp.MustCompile(regexGoImport), + Imports: regexp.MustCompile(regexGoImports), FunctionName: regexp.MustCompile(regexGoFunctionName), FunctionContent: regexp.MustCompile(regexGoFunctionContent), }, } - dir, _ := filepath.Split(path) // create temp dir for building tempDir, err := ioutil.TempDir("", "hrp_build") @@ -224,7 +226,13 @@ func buildGo(path string) error { return err } - outputPath, err := filepath.Abs(filepath.Join(dir, "../debugtalk.bin")) + if output == "" { + dir, _ := os.Getwd() + output = filepath.Join(dir, "debugtalk.bin") + } else if builtin.IsFolderPathExists(output) { + output = filepath.Join(output, "debugtalk.bin") + } + outputPath, err := filepath.Abs(output) if err != nil { return err } @@ -238,7 +246,7 @@ func buildGo(path string) error { } // buildPy completes funppy information in debugtalk.py -func buildPy(path string) error { +func buildPy(path string, output string) error { templateContent := &TemplateContent{ Fun: funppy, Regexps: &Regexps{ @@ -251,8 +259,13 @@ func buildPy(path string) error { } // generate debugtalk.py - dir, _ := filepath.Split(path) - err = templateContent.genDebugTalk(filepath.Join(dir, "../debugtalk.py"), pyTemplate) + if output == "" { + dir, _ := os.Getwd() + output = filepath.Join(dir, "debugtalk.py") + } else if builtin.IsFolderPathExists(output) { + output = filepath.Join(output, "debugtalk.py") + } + err = templateContent.genDebugTalk(output, pyTemplate) if err != nil { return err } @@ -266,39 +279,19 @@ func buildPy(path string) error { return nil } -func Run(args []string) (err error) { - for _, arg := range args { - ext := filepath.Ext(arg) - switch ext { - case ".py": - err = buildPy(arg) - case ".go": - err = buildGo(arg) - default: - return errors.New("type error, expected .py or .go") - } - if err != nil { - log.Error().Err(err).Msg(fmt.Sprintf("failed to build %s", arg)) - os.Exit(1) - } +func Run(arg string, output string) (err error) { + ext := filepath.Ext(arg) + switch ext { + case ".py": + err = buildPy(arg, output) + case ".go": + err = buildGo(arg, output) + default: + return errors.New("type error, expected .py or .go") + } + if err != nil { + log.Error().Err(err).Msg(fmt.Sprintf("failed to build %s", arg)) + os.Exit(1) } return nil } - -// convertSnakeName converts name to snake name -func convertSnakeName(originalName string) string { - snakeName := make([]byte, 0, len(originalName)*2) - flag := false - num := len(originalName) - for i := 0; i < num; i++ { - ch := originalName[i] - if i > 0 && ch >= 'A' && ch <= 'Z' && flag { - snakeName = append(snakeName, '_') - } - if ch != '_' { - flag = true - } - snakeName = append(snakeName, ch) - } - return strings.ToLower(string(snakeName)) -} diff --git a/hrp/internal/build/main_test.go b/hrp/internal/build/main_test.go index ef9e769f..8a58cfe4 100644 --- a/hrp/internal/build/main_test.go +++ b/hrp/internal/build/main_test.go @@ -7,38 +7,23 @@ import ( ) func TestRun(t *testing.T) { - err := Run([]string{"examples/debugtalk_no_funppy.py", "examples/debugtalk_no_fungo.go"}) + err := Run("examples/debugtalk_no_funppy.py", "") + if !assert.Nil(t, err) { + t.Fatal() + } + + err = Run("examples/debugtalk_no_fungo.go", "") + if !assert.Nil(t, err) { + t.Fatal() + } + + err = Run("examples/debugtalk_no_funppy.py", "./debugtalk_gen.py") + if !assert.Nil(t, err) { + t.Fatal() + } + + err = Run("examples/debugtalk_no_fungo.go", "./debugtalk_gen.bin") if !assert.Nil(t, err) { t.Fatal() } } - -func TestConvertSnakeName(t *testing.T) { - testData := []struct { - expectedValue string - originalValue string - }{ - { - expectedValue: "test_name", - originalValue: "testName", - }, - { - expectedValue: "test", - originalValue: "test", - }, - { - expectedValue: "test_name", - originalValue: "TestName", - }, - { - expectedValue: "test_name", - originalValue: "test_name", - }, - } - for _, data := range testData { - name := convertSnakeName(data.originalValue) - if !assert.Equal(t, data.expectedValue, name) { - t.Fatal() - } - } -} diff --git a/hrp/internal/build/templates/debugtalkGoTemplate b/hrp/internal/build/templates/debugtalkGoTemplate index 805d0813..d6d0a95e 100644 --- a/hrp/internal/build/templates/debugtalkGoTemplate +++ b/hrp/internal/build/templates/debugtalkGoTemplate @@ -10,8 +10,8 @@ import ( {{ $function }} {{ end }} func main() { -{{- range $idx, $name := .FunctionNames }} - fungo.Register("{{ index $.FunctionSnakeNames $idx }}", {{ $name }}) +{{- range $idx, $functionName := .FunctionNames }} + fungo.Register("{{ $functionName }}", {{ $functionName }}) {{- end }} fungo.Serve() } diff --git a/hrp/internal/build/templates/debugtalkPythonTemplate b/hrp/internal/build/templates/debugtalkPythonTemplate index d5b209ec..4da17d12 100644 --- a/hrp/internal/build/templates/debugtalkPythonTemplate +++ b/hrp/internal/build/templates/debugtalkPythonTemplate @@ -9,7 +9,7 @@ {{ end }} if __name__ == "__main__": -{{- range $mainRegSnake := .FunctionSnakeNames }} - funppy.register("{{ $mainRegSnake }}", {{ $mainRegSnake }}) +{{- range $functionName := .FunctionNames }} + funppy.register("{{ $functionName }}", {{ $functionName }}) {{- end }} funppy.serve() From 726e5666685e86363d57533ec2de884b9b8565af Mon Sep 17 00:00:00 2001 From: xucong053 <xucong053@outlook.com> Date: Thu, 26 May 2022 21:57:35 +0800 Subject: [PATCH 071/109] fix: default output debugtalk_gen.py by building debugtalk.py --- hrp/internal/build/main.go | 4 ++-- hrp/internal/build/main_test.go | 2 +- hrp/plugin.go | 15 ++++++++++----- 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/hrp/internal/build/main.go b/hrp/internal/build/main.go index afadf8aa..811801ab 100644 --- a/hrp/internal/build/main.go +++ b/hrp/internal/build/main.go @@ -261,9 +261,9 @@ func buildPy(path string, output string) error { // generate debugtalk.py if output == "" { dir, _ := os.Getwd() - output = filepath.Join(dir, "debugtalk.py") + output = filepath.Join(dir, "debugtalk_gen.py") } else if builtin.IsFolderPathExists(output) { - output = filepath.Join(output, "debugtalk.py") + output = filepath.Join(output, "debugtalk_gen.py") } err = templateContent.genDebugTalk(output, pyTemplate) if err != nil { diff --git a/hrp/internal/build/main_test.go b/hrp/internal/build/main_test.go index 8a58cfe4..a169c77a 100644 --- a/hrp/internal/build/main_test.go +++ b/hrp/internal/build/main_test.go @@ -17,7 +17,7 @@ func TestRun(t *testing.T) { t.Fatal() } - err = Run("examples/debugtalk_no_funppy.py", "./debugtalk_gen.py") + err = Run("examples/debugtalk_no_funppy.py", "./debugtalk.py") if !assert.Nil(t, err) { t.Fatal() } diff --git a/hrp/plugin.go b/hrp/plugin.go index f9465a90..004fc1cb 100644 --- a/hrp/plugin.go +++ b/hrp/plugin.go @@ -14,10 +14,10 @@ import ( ) const ( - goPluginFile = "debugtalk.so" // built from go plugin - hashicorpGoPluginFile = "debugtalk.bin" // built from hashicorp go plugin - hashicorpPyPluginFile = "debugtalk.py" // used for hashicorp python plugin - projectInfoFile = "proj.json" // used for ensuring root project + goPluginFile = "debugtalk.so" // built from go plugin + hashicorpGoPluginFile = "debugtalk.bin" // built from hashicorp go plugin + hashicorpPyPluginFile = "debugtalk_gen.py" // used for hashicorp python plugin, automatically generated by HRP + debugtalkPyFile = "debugtalk.py" // write by user ) func initPlugin(path string, logOn bool) (plugin funplugin.IPlugin, pluginDir string, err error) { @@ -62,7 +62,7 @@ func initPlugin(path string, logOn bool) (plugin funplugin.IPlugin, pluginDir st } func locatePlugin(path string) (pluginPath string, err error) { - // priority: hashicorp plugin (debugtalk.bin > debugtalk.py) > go plugin (debugtalk.so) + // priority: hashicorp plugin (debugtalk.bin > debugtalk_gen.py > debugtalk.py) > go plugin (debugtalk.so) pluginPath, err = locateFile(path, hashicorpGoPluginFile) if err == nil { @@ -74,6 +74,11 @@ func locatePlugin(path string) (pluginPath string, err error) { return } + pluginPath, err = locateFile(path, debugtalkPyFile) + if err == nil { + return + } + pluginPath, err = locateFile(path, goPluginFile) if err == nil { return From 5c2be8d5482e1b09f39fa6369d59e033e8b286eb Mon Sep 17 00:00:00 2001 From: xucong053 <xucong053@outlook.com> Date: Fri, 27 May 2022 11:20:21 +0800 Subject: [PATCH 072/109] feat: support v3 format debugtalk.py when executing hrp run/boom --- docs/cmd/hrp.md | 1 + .../demo-with-go-plugin/plugin/debugtalk.go | 14 +- .../demo-with-go-plugin/testcases/demo.json | 8 +- .../testcases/ref_testcase.yml | 2 +- .../testcases/requests.json | 8 +- .../testcases/requests.yml | 8 +- examples/demo-with-no-fungo/.gitignore | 15 ++ examples/demo-with-no-fungo/har/.keep | 0 .../demo-with-no-fungo/plugin/debugtalk.go | 10 +- .../demo-with-no-fungo/testcases/demo.json | 176 ++++++++++++++++++ .../testcases/ref_testcase.yml | 33 ++++ .../testcases/requests.json | 138 ++++++++++++++ .../demo-with-no-fungo/testcases/requests.yml | 65 +++++++ examples/demo-with-no-funppy/.gitignore | 15 ++ .../demo-with-no-funppy/debugtalk.py | 4 + examples/demo-with-no-funppy/har/.keep | 0 .../demo-with-no-funppy/testcases/demo.json | 176 ++++++++++++++++++ .../testcases/ref_testcase.yml | 33 ++++ .../testcases/requests.json | 138 ++++++++++++++ .../testcases/requests.yml | 65 +++++++ hrp/boomer_test.go | 2 +- hrp/internal/build/main.go | 4 +- hrp/internal/build/main_test.go | 30 ++- hrp/internal/builtin/utils.go | 8 +- .../scaffold/templates/plugin/debugtalk.go | 14 +- .../testcases/demo_go_ref_testcase.yml | 33 ++++ .../templates/testcases/demo_go_requests.json | 138 ++++++++++++++ .../templates/testcases/demo_go_requests.yml | 65 +++++++ .../testcases/demo_go_with_funplugin.json | 176 ++++++++++++++++++ hrp/plugin.go | 23 ++- hrp/runner_test.go | 50 +++-- hrp/testcase_test.go | 1 + 32 files changed, 1386 insertions(+), 67 deletions(-) create mode 100644 examples/demo-with-no-fungo/.gitignore create mode 100644 examples/demo-with-no-fungo/har/.keep rename hrp/internal/build/examples/debugtalk_no_fungo.go => examples/demo-with-no-fungo/plugin/debugtalk.go (93%) create mode 100644 examples/demo-with-no-fungo/testcases/demo.json create mode 100644 examples/demo-with-no-fungo/testcases/ref_testcase.yml create mode 100644 examples/demo-with-no-fungo/testcases/requests.json create mode 100644 examples/demo-with-no-fungo/testcases/requests.yml create mode 100644 examples/demo-with-no-funppy/.gitignore rename hrp/internal/build/examples/debugtalk_no_funppy.py => examples/demo-with-no-funppy/debugtalk.py (94%) create mode 100644 examples/demo-with-no-funppy/har/.keep create mode 100644 examples/demo-with-no-funppy/testcases/demo.json create mode 100644 examples/demo-with-no-funppy/testcases/ref_testcase.yml create mode 100644 examples/demo-with-no-funppy/testcases/requests.json create mode 100644 examples/demo-with-no-funppy/testcases/requests.yml create mode 100644 hrp/internal/scaffold/templates/testcases/demo_go_ref_testcase.yml create mode 100644 hrp/internal/scaffold/templates/testcases/demo_go_requests.json create mode 100644 hrp/internal/scaffold/templates/testcases/demo_go_requests.yml create mode 100644 hrp/internal/scaffold/templates/testcases/demo_go_with_funplugin.json diff --git a/docs/cmd/hrp.md b/docs/cmd/hrp.md index c9499319..ce9ba71c 100644 --- a/docs/cmd/hrp.md +++ b/docs/cmd/hrp.md @@ -31,6 +31,7 @@ Copyright 2017 debugtalk * [hrp boom](hrp_boom.md) - run load test with boomer * [hrp convert](hrp_convert.md) - convert to JSON/YAML/gotest/pytest testcases +* [hrp build](hrp_build.md) - build plugin for testing * [hrp har2case](hrp_har2case.md) - convert HAR to json/yaml testcase files * [hrp pytest](hrp_pytest.md) - run API test with pytest * [hrp run](hrp_run.md) - run API test with go engine diff --git a/examples/demo-with-go-plugin/plugin/debugtalk.go b/examples/demo-with-go-plugin/plugin/debugtalk.go index b3b39400..3995ea24 100644 --- a/examples/demo-with-go-plugin/plugin/debugtalk.go +++ b/examples/demo-with-go-plugin/plugin/debugtalk.go @@ -46,12 +46,12 @@ func GetVersion() string { } func main() { - fungo.Register("get_version", GetVersion) - fungo.Register("sum_ints", SumInts) - fungo.Register("sum_two_int", SumTwoInt) - fungo.Register("sum_two", SumTwoInt) - fungo.Register("sum", Sum) - fungo.Register("setup_hook_example", SetupHookExample) - fungo.Register("teardown_hook_example", TeardownHookExample) + fungo.Register("GetVersion", GetVersion) + fungo.Register("SumInts", SumInts) + fungo.Register("SumTwoInt", SumTwoInt) + fungo.Register("SumTwoInt", SumTwoInt) + fungo.Register("Sum", Sum) + fungo.Register("SetupHookExample", SetupHookExample) + fungo.Register("TeardownHookExample", TeardownHookExample) fungo.Serve() } diff --git a/examples/demo-with-go-plugin/testcases/demo.json b/examples/demo-with-go-plugin/testcases/demo.json index 1bb63ed8..0af40519 100644 --- a/examples/demo-with-go-plugin/testcases/demo.json +++ b/examples/demo-with-go-plugin/testcases/demo.json @@ -3,9 +3,9 @@ "name": "demo with complex mechanisms", "base_url": "https://postman-echo.com", "variables": { - "a": "${sum(10, 2.3)}", + "a": "${Sum(10, 2.3)}", "b": 3.45, - "n": "${sum_ints(1, 2, 2)}", + "n": "${SumInts(1, 2, 2)}", "varFoo1": "${gen_random_string($n)}", "varFoo2": "${max($a, $b)}" } @@ -38,10 +38,10 @@ "varFoo2": "${max($a, $b)}" }, "setup_hooks": [ - "${setup_hook_example($name)}" + "${SetupHookExample($name)}" ], "teardown_hooks": [ - "${teardown_hook_example($name)}" + "${TeardownHookExample($name)}" ], "extract": { "varFoo1": "body.args.foo1" diff --git a/examples/demo-with-go-plugin/testcases/ref_testcase.yml b/examples/demo-with-go-plugin/testcases/ref_testcase.yml index 0816481c..010133cf 100644 --- a/examples/demo-with-go-plugin/testcases/ref_testcase.yml +++ b/examples/demo-with-go-plugin/testcases/ref_testcase.yml @@ -24,7 +24,7 @@ teststeps: method: POST url: /post headers: - User-Agent: funplugin/${get_version()} + User-Agent: funplugin/${GetVersion()} Content-Type: "application/x-www-form-urlencoded" body: "foo1=$foo1&foo2=$foo3" validate: diff --git a/examples/demo-with-go-plugin/testcases/requests.json b/examples/demo-with-go-plugin/testcases/requests.json index 162632b4..d4dcb276 100644 --- a/examples/demo-with-go-plugin/testcases/requests.json +++ b/examples/demo-with-go-plugin/testcases/requests.json @@ -19,7 +19,7 @@ "variables": { "foo1": "${ENV(USERNAME)}", "foo2": "bar21", - "sum_v": "${sum_two_int(1, 2)}" + "sum_v": "${SumTwoInt(1, 2)}" }, "request": { "method": "GET", @@ -30,7 +30,7 @@ "sum_v": "$sum_v" }, "headers": { - "User-Agent": "funplugin/${get_version()}" + "User-Agent": "funplugin/${GetVersion()}" } }, "extract": { @@ -73,7 +73,7 @@ "method": "POST", "url": "/post", "headers": { - "User-Agent": "funplugin/${get_version()}", + "User-Agent": "funplugin/${GetVersion()}", "Content-Type": "text/plain" }, "body": "This is expected to be sent back as part of response body: $foo1-$foo2-$foo3." @@ -102,7 +102,7 @@ "method": "POST", "url": "/post", "headers": { - "User-Agent": "funplugin/${get_version()}", + "User-Agent": "funplugin/${GetVersion()}", "Content-Type": "application/x-www-form-urlencoded" }, "body": "foo1=$foo1&foo2=$foo2&foo3=$foo3" diff --git a/examples/demo-with-go-plugin/testcases/requests.yml b/examples/demo-with-go-plugin/testcases/requests.yml index 034dbefb..add3a28d 100644 --- a/examples/demo-with-go-plugin/testcases/requests.yml +++ b/examples/demo-with-go-plugin/testcases/requests.yml @@ -14,7 +14,7 @@ teststeps: variables: foo1: ${ENV(USERNAME)} foo2: bar21 - sum_v: "${sum_two_int(1, 2)}" + sum_v: "${SumTwoInt(1, 2)}" request: method: GET url: $base_url/get @@ -23,7 +23,7 @@ teststeps: foo2: $foo2 sum_v: $sum_v headers: - User-Agent: funplugin/${get_version()} + User-Agent: funplugin/${GetVersion()} extract: foo3: "body.args.foo2" validate: @@ -40,7 +40,7 @@ teststeps: method: POST url: $base_url/post headers: - User-Agent: funplugin/${get_version()} + User-Agent: funplugin/${GetVersion()} Content-Type: "text/plain" body: "This is expected to be sent back as part of response body: $foo1-$foo2-$foo3." validate: @@ -54,7 +54,7 @@ teststeps: method: POST url: $base_url/post headers: - User-Agent: funplugin/${get_version()} + User-Agent: funplugin/${GetVersion()} Content-Type: "application/x-www-form-urlencoded" body: "foo1=$foo1&foo2=$foo2&foo3=$foo3" validate: diff --git a/examples/demo-with-no-fungo/.gitignore b/examples/demo-with-no-fungo/.gitignore new file mode 100644 index 00000000..33401380 --- /dev/null +++ b/examples/demo-with-no-fungo/.gitignore @@ -0,0 +1,15 @@ +.env +reports/ +*.so +.vscode/ +.idea/ +.DS_Store +output/ +__pycache__/ +*.pyc +.python-version +logs/ + +# plugin +debugtalk.bin +debugtalk.so diff --git a/examples/demo-with-no-fungo/har/.keep b/examples/demo-with-no-fungo/har/.keep new file mode 100644 index 00000000..e69de29b diff --git a/hrp/internal/build/examples/debugtalk_no_fungo.go b/examples/demo-with-no-fungo/plugin/debugtalk.go similarity index 93% rename from hrp/internal/build/examples/debugtalk_no_fungo.go rename to examples/demo-with-no-fungo/plugin/debugtalk.go index 12d5ce57..5810e0e4 100644 --- a/hrp/internal/build/examples/debugtalk_no_fungo.go +++ b/examples/demo-with-no-fungo/plugin/debugtalk.go @@ -1,10 +1,12 @@ -package examples +package main import ( "fmt" ) -import "os" +func init() { + fmt.Println("init") +} func SumTwoInt(a, b int) int { return a + b @@ -40,7 +42,3 @@ func SetupHookExample(args string) string { func TeardownHookExample(args string) string { return fmt.Sprintf("step name: %v, teardown...", args) } - -func init() { - _, _ = os.Getwd() -} diff --git a/examples/demo-with-no-fungo/testcases/demo.json b/examples/demo-with-no-fungo/testcases/demo.json new file mode 100644 index 00000000..a127d26d --- /dev/null +++ b/examples/demo-with-no-fungo/testcases/demo.json @@ -0,0 +1,176 @@ +{ + "config": { + "name": "demo with complex mechanisms", + "base_url": "https://postman-echo.com", + "variables": { + "a": "${Sum(10, 2.3)}", + "b": 3.45, + "n": "${SumInts(1, 2, 2)}", + "varFoo1": "${GenRandomString($n)}", + "varFoo2": "${Max($a, $b)}" + } + }, + "teststeps": [ + { + "name": "transaction 1 start", + "transaction": { + "name": "tran1", + "type": "start" + } + }, + { + "name": "get with params", + "request": { + "method": "GET", + "url": "/get", + "params": { + "foo1": "$varFoo1", + "foo2": "$varFoo2" + }, + "headers": { + "User-Agent": "HttpRunnerPlus" + } + }, + "variables": { + "b": 34.5, + "n": 3, + "name": "get with params", + "varFoo2": "${Max($a, $b)}" + }, + "setup_hooks": [ + "${SetupHookExample($name)}" + ], + "teardown_hooks": [ + "${TeardownHookExample($name)}" + ], + "extract": { + "varFoo1": "body.args.foo1" + }, + "validate": [ + { + "check": "status_code", + "assert": "equals", + "expect": 200, + "msg": "check response status code" + }, + { + "check": "headers.\"Content-Type\"", + "assert": "startswith", + "expect": "application/json" + }, + { + "check": "body.args.foo1", + "assert": "length_equals", + "expect": 5, + "msg": "check args foo1" + }, + { + "check": "$varFoo1", + "assert": "length_equals", + "expect": 5, + "msg": "check args foo1" + }, + { + "check": "body.args.foo2", + "assert": "equals", + "expect": "34.5", + "msg": "check args foo2" + } + ] + }, + { + "name": "transaction 1 end", + "transaction": { + "name": "tran1", + "type": "end" + } + }, + { + "name": "post json data", + "request": { + "method": "POST", + "url": "/post", + "body": { + "foo1": "$varFoo1", + "foo2": "${Max($a, $b)}" + } + }, + "validate": [ + { + "check": "status_code", + "assert": "equals", + "expect": 200, + "msg": "check status code" + }, + { + "check": "body.json.foo1", + "assert": "length_equals", + "expect": 5, + "msg": "check args foo1" + }, + { + "check": "body.json.foo2", + "assert": "equals", + "expect": 12.3, + "msg": "check args foo2" + } + ] + }, + { + "name": "post form data", + "request": { + "method": "POST", + "url": "/post", + "headers": { + "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8" + }, + "body": { + "foo1": "$varFoo1", + "foo2": "${Max($a, $b)}", + "time": "${GetTimestamp()}" + } + }, + "extract": { + "varTime": "body.form.time" + }, + "validate": [ + { + "check": "status_code", + "assert": "equals", + "expect": 200, + "msg": "check status code" + }, + { + "check": "body.form.foo1", + "assert": "length_equals", + "expect": 5, + "msg": "check args foo1" + }, + { + "check": "body.form.foo2", + "assert": "equals", + "expect": "12.3", + "msg": "check args foo2" + } + ] + }, + { + "name": "get with timestamp", + "request": { + "method": "GET", + "url": "/get", + "params": { + "time": "$varTime" + } + }, + "validate": [ + { + "check": "body.args.time", + "assert": "length_equals", + "expect": 13, + "msg": "check extracted var timestamp" + } + ] + } + ] +} \ No newline at end of file diff --git a/examples/demo-with-no-fungo/testcases/ref_testcase.yml b/examples/demo-with-no-fungo/testcases/ref_testcase.yml new file mode 100644 index 00000000..957dbdd4 --- /dev/null +++ b/examples/demo-with-no-fungo/testcases/ref_testcase.yml @@ -0,0 +1,33 @@ +config: + name: "request methods testcase: reference testcase" + variables: + foo1: testsuite_config_bar1 + expect_foo1: testsuite_config_bar1 + expect_foo2: config_bar2 + base_url: "https://postman-echo.com" + verify: False + +teststeps: +- + name: request with functions + variables: + foo1: testcase_ref_bar1 + expect_foo1: testcase_ref_bar1 + testcase: testcases/requests.yml + export: + - foo3 +- + name: post form data + variables: + foo1: bar1 + request: + method: POST + url: /post + headers: + User-Agent: funplugin/${GetVersion()} + Content-Type: "application/x-www-form-urlencoded" + data: "foo1=$foo1&foo2=$foo3" + validate: + - eq: ["status_code", 200] + - eq: ["body.form.foo1", "bar1"] + - eq: ["body.form.foo2", "bar21"] diff --git a/examples/demo-with-no-fungo/testcases/requests.json b/examples/demo-with-no-fungo/testcases/requests.json new file mode 100644 index 00000000..f54e6340 --- /dev/null +++ b/examples/demo-with-no-fungo/testcases/requests.json @@ -0,0 +1,138 @@ +{ + "config": { + "name": "request methods testcase with functions", + "variables": { + "foo1": "config_bar1", + "foo2": "config_bar2", + "expect_foo1": "config_bar1", + "expect_foo2": "config_bar2" + }, + "base_url": "https://postman-echo.com", + "verify": false, + "export": [ + "foo3" + ] + }, + "teststeps": [ + { + "name": "get with params", + "variables": { + "foo1": "bar11", + "foo2": "bar21", + "sum_v": "${SumTwoInt(1, 2)}" + }, + "request": { + "method": "GET", + "url": "/get", + "params": { + "foo1": "$foo1", + "foo2": "$foo2", + "sum_v": "$sum_v" + }, + "headers": { + "User-Agent": "funplugin/${GetVersion()}" + } + }, + "extract": { + "foo3": "body.args.foo2" + }, + "validate": [ + { + "eq": [ + "status_code", + 200 + ] + }, + { + "eq": [ + "body.args.foo1", + "bar11" + ] + }, + { + "eq": [ + "body.args.sum_v", + "3" + ] + }, + { + "eq": [ + "body.args.foo2", + "bar21" + ] + } + ] + }, + { + "name": "post raw text", + "variables": { + "foo1": "bar12", + "foo3": "bar32" + }, + "request": { + "method": "POST", + "url": "/post", + "headers": { + "User-Agent": "funplugin/${GetVersion()}", + "Content-Type": "text/plain" + }, + "data": "This is expected to be sent back as part of response body: $foo1-$foo2-$foo3." + }, + "validate": [ + { + "eq": [ + "status_code", + 200 + ] + }, + { + "eq": [ + "body.data", + "This is expected to be sent back as part of response body: bar12-$expect_foo2-bar32." + ] + } + ] + }, + { + "name": "post form data", + "variables": { + "foo2": "bar23" + }, + "request": { + "method": "POST", + "url": "/post", + "headers": { + "User-Agent": "funplugin/${GetVersion()}", + "Content-Type": "application/x-www-form-urlencoded" + }, + "data": "foo1=$foo1&foo2=$foo2&foo3=$foo3" + }, + "validate": [ + { + "eq": [ + "status_code", + 200 + ] + }, + { + "eq": [ + "body.form.foo1", + "$expect_foo1" + ] + }, + { + "eq": [ + "body.form.foo2", + "bar23" + ] + }, + { + "eq": [ + "body.form.foo3", + "bar21" + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/examples/demo-with-no-fungo/testcases/requests.yml b/examples/demo-with-no-fungo/testcases/requests.yml new file mode 100644 index 00000000..7884588f --- /dev/null +++ b/examples/demo-with-no-fungo/testcases/requests.yml @@ -0,0 +1,65 @@ +config: + name: "request methods testcase with functions" + variables: + foo1: config_bar1 + foo2: config_bar2 + expect_foo1: config_bar1 + expect_foo2: config_bar2 + base_url: "https://postman-echo.com" + verify: False + export: ["foo3"] + +teststeps: +- + name: get with params + variables: + foo1: bar11 + foo2: bar21 + sum_v: "${SumTwoInt(1, 2)}" + request: + method: GET + url: /get + params: + foo1: $foo1 + foo2: $foo2 + sum_v: $sum_v + headers: + User-Agent: funplugin/${GetVersion()} + extract: + foo3: "body.args.foo2" + validate: + - eq: ["status_code", 200] + - eq: ["body.args.foo1", "bar11"] + - eq: ["body.args.sum_v", "3"] + - eq: ["body.args.foo2", "bar21"] +- + name: post raw text + variables: + foo1: "bar12" + foo3: "bar32" + request: + method: POST + url: /post + headers: + User-Agent: funplugin/${GetVersion()} + Content-Type: "text/plain" + data: "This is expected to be sent back as part of response body: $foo1-$foo2-$foo3." + validate: + - eq: ["status_code", 200] + - eq: ["body.data", "This is expected to be sent back as part of response body: bar12-$expect_foo2-bar32."] +- + name: post form data + variables: + foo2: bar23 + request: + method: POST + url: /post + headers: + User-Agent: funplugin/${GetVersion()} + Content-Type: "application/x-www-form-urlencoded" + data: "foo1=$foo1&foo2=$foo2&foo3=$foo3" + validate: + - eq: ["status_code", 200] + - eq: ["body.form.foo1", "$expect_foo1"] + - eq: ["body.form.foo2", "bar23"] + - eq: ["body.form.foo3", "bar21"] diff --git a/examples/demo-with-no-funppy/.gitignore b/examples/demo-with-no-funppy/.gitignore new file mode 100644 index 00000000..33401380 --- /dev/null +++ b/examples/demo-with-no-funppy/.gitignore @@ -0,0 +1,15 @@ +.env +reports/ +*.so +.vscode/ +.idea/ +.DS_Store +output/ +__pycache__/ +*.pyc +.python-version +logs/ + +# plugin +debugtalk.bin +debugtalk.so diff --git a/hrp/internal/build/examples/debugtalk_no_funppy.py b/examples/demo-with-no-funppy/debugtalk.py similarity index 94% rename from hrp/internal/build/examples/debugtalk_no_funppy.py rename to examples/demo-with-no-funppy/debugtalk.py index 370206d6..8d93ae1f 100644 --- a/hrp/internal/build/examples/debugtalk_no_funppy.py +++ b/examples/demo-with-no-funppy/debugtalk.py @@ -3,6 +3,10 @@ import time from typing import List +def get_version(): + return "httprunner v4.0" + + def sleep(n_secs): time.sleep(n_secs) diff --git a/examples/demo-with-no-funppy/har/.keep b/examples/demo-with-no-funppy/har/.keep new file mode 100644 index 00000000..e69de29b diff --git a/examples/demo-with-no-funppy/testcases/demo.json b/examples/demo-with-no-funppy/testcases/demo.json new file mode 100644 index 00000000..1bb63ed8 --- /dev/null +++ b/examples/demo-with-no-funppy/testcases/demo.json @@ -0,0 +1,176 @@ +{ + "config": { + "name": "demo with complex mechanisms", + "base_url": "https://postman-echo.com", + "variables": { + "a": "${sum(10, 2.3)}", + "b": 3.45, + "n": "${sum_ints(1, 2, 2)}", + "varFoo1": "${gen_random_string($n)}", + "varFoo2": "${max($a, $b)}" + } + }, + "teststeps": [ + { + "name": "transaction 1 start", + "transaction": { + "name": "tran1", + "type": "start" + } + }, + { + "name": "get with params", + "request": { + "method": "GET", + "url": "/get", + "params": { + "foo1": "$varFoo1", + "foo2": "$varFoo2" + }, + "headers": { + "User-Agent": "HttpRunnerPlus" + } + }, + "variables": { + "b": 34.5, + "n": 3, + "name": "get with params", + "varFoo2": "${max($a, $b)}" + }, + "setup_hooks": [ + "${setup_hook_example($name)}" + ], + "teardown_hooks": [ + "${teardown_hook_example($name)}" + ], + "extract": { + "varFoo1": "body.args.foo1" + }, + "validate": [ + { + "check": "status_code", + "assert": "equals", + "expect": 200, + "msg": "check response status code" + }, + { + "check": "headers.\"Content-Type\"", + "assert": "startswith", + "expect": "application/json" + }, + { + "check": "body.args.foo1", + "assert": "length_equals", + "expect": 5, + "msg": "check args foo1" + }, + { + "check": "$varFoo1", + "assert": "length_equals", + "expect": 5, + "msg": "check args foo1" + }, + { + "check": "body.args.foo2", + "assert": "equals", + "expect": "34.5", + "msg": "check args foo2" + } + ] + }, + { + "name": "transaction 1 end", + "transaction": { + "name": "tran1", + "type": "end" + } + }, + { + "name": "post json data", + "request": { + "method": "POST", + "url": "/post", + "body": { + "foo1": "$varFoo1", + "foo2": "${max($a, $b)}" + } + }, + "validate": [ + { + "check": "status_code", + "assert": "equals", + "expect": 200, + "msg": "check status code" + }, + { + "check": "body.json.foo1", + "assert": "length_equals", + "expect": 5, + "msg": "check args foo1" + }, + { + "check": "body.json.foo2", + "assert": "equals", + "expect": 12.3, + "msg": "check args foo2" + } + ] + }, + { + "name": "post form data", + "request": { + "method": "POST", + "url": "/post", + "headers": { + "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8" + }, + "body": { + "foo1": "$varFoo1", + "foo2": "${max($a, $b)}", + "time": "${get_timestamp()}" + } + }, + "extract": { + "varTime": "body.form.time" + }, + "validate": [ + { + "check": "status_code", + "assert": "equals", + "expect": 200, + "msg": "check status code" + }, + { + "check": "body.form.foo1", + "assert": "length_equals", + "expect": 5, + "msg": "check args foo1" + }, + { + "check": "body.form.foo2", + "assert": "equals", + "expect": "12.3", + "msg": "check args foo2" + } + ] + }, + { + "name": "get with timestamp", + "request": { + "method": "GET", + "url": "/get", + "params": { + "time": "$varTime" + } + }, + "validate": [ + { + "check": "body.args.time", + "assert": "length_equals", + "expect": 13, + "msg": "check extracted var timestamp" + } + ] + } + ] +} \ No newline at end of file diff --git a/examples/demo-with-no-funppy/testcases/ref_testcase.yml b/examples/demo-with-no-funppy/testcases/ref_testcase.yml new file mode 100644 index 00000000..6cf32323 --- /dev/null +++ b/examples/demo-with-no-funppy/testcases/ref_testcase.yml @@ -0,0 +1,33 @@ +config: + name: "request methods testcase: reference testcase" + variables: + foo1: testsuite_config_bar1 + expect_foo1: testsuite_config_bar1 + expect_foo2: config_bar2 + base_url: "https://postman-echo.com" + verify: False + +teststeps: +- + name: request with functions + variables: + foo1: testcase_ref_bar1 + expect_foo1: testcase_ref_bar1 + testcase: testcases/requests.yml + export: + - foo3 +- + name: post form data + variables: + foo1: bar1 + request: + method: POST + url: /post + headers: + User-Agent: funplugin/${get_version()} + Content-Type: "application/x-www-form-urlencoded" + data: "foo1=$foo1&foo2=$foo3" + validate: + - eq: ["status_code", 200] + - eq: ["body.form.foo1", "bar1"] + - eq: ["body.form.foo2", "bar21"] diff --git a/examples/demo-with-no-funppy/testcases/requests.json b/examples/demo-with-no-funppy/testcases/requests.json new file mode 100644 index 00000000..b13f3837 --- /dev/null +++ b/examples/demo-with-no-funppy/testcases/requests.json @@ -0,0 +1,138 @@ +{ + "config": { + "name": "request methods testcase with functions", + "variables": { + "foo1": "config_bar1", + "foo2": "config_bar2", + "expect_foo1": "config_bar1", + "expect_foo2": "config_bar2" + }, + "base_url": "https://postman-echo.com", + "verify": false, + "export": [ + "foo3" + ] + }, + "teststeps": [ + { + "name": "get with params", + "variables": { + "foo1": "bar11", + "foo2": "bar21", + "sum_v": "${sum_two_int(1, 2)}" + }, + "request": { + "method": "GET", + "url": "/get", + "params": { + "foo1": "$foo1", + "foo2": "$foo2", + "sum_v": "$sum_v" + }, + "headers": { + "User-Agent": "funplugin/${get_version()}" + } + }, + "extract": { + "foo3": "body.args.foo2" + }, + "validate": [ + { + "eq": [ + "status_code", + 200 + ] + }, + { + "eq": [ + "body.args.foo1", + "bar11" + ] + }, + { + "eq": [ + "body.args.sum_v", + "3" + ] + }, + { + "eq": [ + "body.args.foo2", + "bar21" + ] + } + ] + }, + { + "name": "post raw text", + "variables": { + "foo1": "bar12", + "foo3": "bar32" + }, + "request": { + "method": "POST", + "url": "/post", + "headers": { + "User-Agent": "funplugin/${get_version()}", + "Content-Type": "text/plain" + }, + "data": "This is expected to be sent back as part of response body: $foo1-$foo2-$foo3." + }, + "validate": [ + { + "eq": [ + "status_code", + 200 + ] + }, + { + "eq": [ + "body.data", + "This is expected to be sent back as part of response body: bar12-$expect_foo2-bar32." + ] + } + ] + }, + { + "name": "post form data", + "variables": { + "foo2": "bar23" + }, + "request": { + "method": "POST", + "url": "/post", + "headers": { + "User-Agent": "funplugin/${get_version()}", + "Content-Type": "application/x-www-form-urlencoded" + }, + "data": "foo1=$foo1&foo2=$foo2&foo3=$foo3" + }, + "validate": [ + { + "eq": [ + "status_code", + 200 + ] + }, + { + "eq": [ + "body.form.foo1", + "$expect_foo1" + ] + }, + { + "eq": [ + "body.form.foo2", + "bar23" + ] + }, + { + "eq": [ + "body.form.foo3", + "bar21" + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/examples/demo-with-no-funppy/testcases/requests.yml b/examples/demo-with-no-funppy/testcases/requests.yml new file mode 100644 index 00000000..86d1b9cc --- /dev/null +++ b/examples/demo-with-no-funppy/testcases/requests.yml @@ -0,0 +1,65 @@ +config: + name: "request methods testcase with functions" + variables: + foo1: config_bar1 + foo2: config_bar2 + expect_foo1: config_bar1 + expect_foo2: config_bar2 + base_url: "https://postman-echo.com" + verify: False + export: ["foo3"] + +teststeps: +- + name: get with params + variables: + foo1: bar11 + foo2: bar21 + sum_v: "${sum_two_int(1, 2)}" + request: + method: GET + url: /get + params: + foo1: $foo1 + foo2: $foo2 + sum_v: $sum_v + headers: + User-Agent: funplugin/${get_version()} + extract: + foo3: "body.args.foo2" + validate: + - eq: ["status_code", 200] + - eq: ["body.args.foo1", "bar11"] + - eq: ["body.args.sum_v", "3"] + - eq: ["body.args.foo2", "bar21"] +- + name: post raw text + variables: + foo1: "bar12" + foo3: "bar32" + request: + method: POST + url: /post + headers: + User-Agent: funplugin/${get_version()} + Content-Type: "text/plain" + data: "This is expected to be sent back as part of response body: $foo1-$foo2-$foo3." + validate: + - eq: ["status_code", 200] + - eq: ["body.data", "This is expected to be sent back as part of response body: bar12-$expect_foo2-bar32."] +- + name: post form data + variables: + foo2: bar23 + request: + method: POST + url: /post + headers: + User-Agent: funplugin/${get_version()} + Content-Type: "application/x-www-form-urlencoded" + data: "foo1=$foo1&foo2=$foo2&foo3=$foo3" + validate: + - eq: ["status_code", 200] + - eq: ["body.form.foo1", "$expect_foo1"] + - eq: ["body.form.foo2", "bar23"] + - eq: ["body.form.foo3", "bar21"] diff --git a/hrp/boomer_test.go b/hrp/boomer_test.go index 4edefa38..b70fc832 100644 --- a/hrp/boomer_test.go +++ b/hrp/boomer_test.go @@ -25,7 +25,7 @@ func TestBoomerStandaloneRun(t *testing.T) { NewStep("TestCase3").CallRefCase(&TestCase{Config: NewConfig("TestCase3")}), }, } - testcase2 := &demoTestCaseWithPluginJSONPath + testcase2 := &demoTestCaseWithGoPluginJSONPath b := NewBoomer(2, 1) go b.Run(testcase1, testcase2) diff --git a/hrp/internal/build/main.go b/hrp/internal/build/main.go index 811801ab..39c2e92a 100644 --- a/hrp/internal/build/main.go +++ b/hrp/internal/build/main.go @@ -62,7 +62,7 @@ func (t *TemplateContent) parseGoContent(path string) error { originalContent := string(content) // parse imports - importSlice := t.Regexps.Import.FindAllStringSubmatch(originalContent, -1) + importSlice := t.Regexps.Imports.FindAllStringSubmatch(originalContent, -1) if len(importSlice) != 0 { imports := strings.Replace(importSlice[0][1], "\t", "", -1) for _, elem := range strings.Split(imports, "\n") { @@ -70,7 +70,7 @@ func (t *TemplateContent) parseGoContent(path string) error { } } // parse import - importSlice = t.Regexps.Imports.FindAllStringSubmatch(originalContent, -1) + importSlice = t.Regexps.Import.FindAllStringSubmatch(originalContent, -1) if len(importSlice) != 0 { for _, elem := range importSlice { t.Imports = append(t.Imports, strings.TrimSpace(elem[1])) diff --git a/hrp/internal/build/main_test.go b/hrp/internal/build/main_test.go index a169c77a..b26109a2 100644 --- a/hrp/internal/build/main_test.go +++ b/hrp/internal/build/main_test.go @@ -1,29 +1,51 @@ package build import ( + "github.com/httprunner/httprunner/v4/hrp/internal/builtin" + "regexp" "testing" "github.com/stretchr/testify/assert" ) func TestRun(t *testing.T) { - err := Run("examples/debugtalk_no_funppy.py", "") + err := Run("../../../examples/demo-with-no-fungo/plugin/debugtalk.go", "") if !assert.Nil(t, err) { t.Fatal() } - err = Run("examples/debugtalk_no_fungo.go", "") + err = Run("../../../examples/demo-with-no-funppy/debugtalk.py", "") if !assert.Nil(t, err) { t.Fatal() } - err = Run("examples/debugtalk_no_funppy.py", "./debugtalk.py") + err = Run("../../../examples/demo-with-no-fungo/plugin/debugtalk.go", "./debugtalk_gen.bin") if !assert.Nil(t, err) { t.Fatal() } - err = Run("examples/debugtalk_no_fungo.go", "./debugtalk_gen.bin") + err = Run("../../../examples/demo-with-no-funppy/debugtalk.py", "./debugtalk_gen.py") if !assert.Nil(t, err) { t.Fatal() } + + contentBytes, err := builtin.ReadFile("./debugtalk_gen.py") + if !assert.Nil(t, err) { + t.Fatal() + } + + content := string(contentBytes) + if !assert.Contains(t, content, "import funppy") { + t.Fatal() + } + + if !assert.Contains(t, content, "funppy.register") { + t.Fatal() + } + + reg, _ := regexp.Compile(`funppy\.register`) + matchedSlice := reg.FindAllStringSubmatch(content, -1) + if !assert.Len(t, matchedSlice, 10) { + t.Fatal() + } } diff --git a/hrp/internal/builtin/utils.go b/hrp/internal/builtin/utils.go index d4920084..09176168 100644 --- a/hrp/internal/builtin/utils.go +++ b/hrp/internal/builtin/utils.go @@ -281,7 +281,7 @@ var ErrUnsupportedFileExt = fmt.Errorf("unsupported file extension") // LoadFile loads file content with file extension and assigns to structObj func LoadFile(path string, structObj interface{}) (err error) { log.Info().Str("path", path).Msg("load file") - file, err := readFile(path) + file, err := ReadFile(path) if err != nil { return errors.Wrap(err, "read file failed") } @@ -335,7 +335,7 @@ func parseEnvContent(file []byte, obj interface{}) error { func loadFromCSV(path string) []map[string]interface{} { log.Info().Str("path", path).Msg("load csv file") - file, err := readFile(path) + file, err := ReadFile(path) if err != nil { log.Error().Err(err).Msg("read csv file failed") os.Exit(1) @@ -361,7 +361,7 @@ func loadFromCSV(path string) []map[string]interface{} { func loadMessage(path string) []byte { log.Info().Str("path", path).Msg("load message file") - file, err := readFile(path) + file, err := ReadFile(path) if err != nil { log.Error().Err(err).Msg("read message file failed") os.Exit(1) @@ -369,7 +369,7 @@ func loadMessage(path string) []byte { return file } -func readFile(path string) ([]byte, error) { +func ReadFile(path string) ([]byte, error) { var err error path, err = filepath.Abs(path) if err != nil { diff --git a/hrp/internal/scaffold/templates/plugin/debugtalk.go b/hrp/internal/scaffold/templates/plugin/debugtalk.go index b3b39400..3995ea24 100644 --- a/hrp/internal/scaffold/templates/plugin/debugtalk.go +++ b/hrp/internal/scaffold/templates/plugin/debugtalk.go @@ -46,12 +46,12 @@ func GetVersion() string { } func main() { - fungo.Register("get_version", GetVersion) - fungo.Register("sum_ints", SumInts) - fungo.Register("sum_two_int", SumTwoInt) - fungo.Register("sum_two", SumTwoInt) - fungo.Register("sum", Sum) - fungo.Register("setup_hook_example", SetupHookExample) - fungo.Register("teardown_hook_example", TeardownHookExample) + fungo.Register("GetVersion", GetVersion) + fungo.Register("SumInts", SumInts) + fungo.Register("SumTwoInt", SumTwoInt) + fungo.Register("SumTwoInt", SumTwoInt) + fungo.Register("Sum", Sum) + fungo.Register("SetupHookExample", SetupHookExample) + fungo.Register("TeardownHookExample", TeardownHookExample) fungo.Serve() } diff --git a/hrp/internal/scaffold/templates/testcases/demo_go_ref_testcase.yml b/hrp/internal/scaffold/templates/testcases/demo_go_ref_testcase.yml new file mode 100644 index 00000000..957dbdd4 --- /dev/null +++ b/hrp/internal/scaffold/templates/testcases/demo_go_ref_testcase.yml @@ -0,0 +1,33 @@ +config: + name: "request methods testcase: reference testcase" + variables: + foo1: testsuite_config_bar1 + expect_foo1: testsuite_config_bar1 + expect_foo2: config_bar2 + base_url: "https://postman-echo.com" + verify: False + +teststeps: +- + name: request with functions + variables: + foo1: testcase_ref_bar1 + expect_foo1: testcase_ref_bar1 + testcase: testcases/requests.yml + export: + - foo3 +- + name: post form data + variables: + foo1: bar1 + request: + method: POST + url: /post + headers: + User-Agent: funplugin/${GetVersion()} + Content-Type: "application/x-www-form-urlencoded" + data: "foo1=$foo1&foo2=$foo3" + validate: + - eq: ["status_code", 200] + - eq: ["body.form.foo1", "bar1"] + - eq: ["body.form.foo2", "bar21"] diff --git a/hrp/internal/scaffold/templates/testcases/demo_go_requests.json b/hrp/internal/scaffold/templates/testcases/demo_go_requests.json new file mode 100644 index 00000000..f54e6340 --- /dev/null +++ b/hrp/internal/scaffold/templates/testcases/demo_go_requests.json @@ -0,0 +1,138 @@ +{ + "config": { + "name": "request methods testcase with functions", + "variables": { + "foo1": "config_bar1", + "foo2": "config_bar2", + "expect_foo1": "config_bar1", + "expect_foo2": "config_bar2" + }, + "base_url": "https://postman-echo.com", + "verify": false, + "export": [ + "foo3" + ] + }, + "teststeps": [ + { + "name": "get with params", + "variables": { + "foo1": "bar11", + "foo2": "bar21", + "sum_v": "${SumTwoInt(1, 2)}" + }, + "request": { + "method": "GET", + "url": "/get", + "params": { + "foo1": "$foo1", + "foo2": "$foo2", + "sum_v": "$sum_v" + }, + "headers": { + "User-Agent": "funplugin/${GetVersion()}" + } + }, + "extract": { + "foo3": "body.args.foo2" + }, + "validate": [ + { + "eq": [ + "status_code", + 200 + ] + }, + { + "eq": [ + "body.args.foo1", + "bar11" + ] + }, + { + "eq": [ + "body.args.sum_v", + "3" + ] + }, + { + "eq": [ + "body.args.foo2", + "bar21" + ] + } + ] + }, + { + "name": "post raw text", + "variables": { + "foo1": "bar12", + "foo3": "bar32" + }, + "request": { + "method": "POST", + "url": "/post", + "headers": { + "User-Agent": "funplugin/${GetVersion()}", + "Content-Type": "text/plain" + }, + "data": "This is expected to be sent back as part of response body: $foo1-$foo2-$foo3." + }, + "validate": [ + { + "eq": [ + "status_code", + 200 + ] + }, + { + "eq": [ + "body.data", + "This is expected to be sent back as part of response body: bar12-$expect_foo2-bar32." + ] + } + ] + }, + { + "name": "post form data", + "variables": { + "foo2": "bar23" + }, + "request": { + "method": "POST", + "url": "/post", + "headers": { + "User-Agent": "funplugin/${GetVersion()}", + "Content-Type": "application/x-www-form-urlencoded" + }, + "data": "foo1=$foo1&foo2=$foo2&foo3=$foo3" + }, + "validate": [ + { + "eq": [ + "status_code", + 200 + ] + }, + { + "eq": [ + "body.form.foo1", + "$expect_foo1" + ] + }, + { + "eq": [ + "body.form.foo2", + "bar23" + ] + }, + { + "eq": [ + "body.form.foo3", + "bar21" + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/hrp/internal/scaffold/templates/testcases/demo_go_requests.yml b/hrp/internal/scaffold/templates/testcases/demo_go_requests.yml new file mode 100644 index 00000000..7884588f --- /dev/null +++ b/hrp/internal/scaffold/templates/testcases/demo_go_requests.yml @@ -0,0 +1,65 @@ +config: + name: "request methods testcase with functions" + variables: + foo1: config_bar1 + foo2: config_bar2 + expect_foo1: config_bar1 + expect_foo2: config_bar2 + base_url: "https://postman-echo.com" + verify: False + export: ["foo3"] + +teststeps: +- + name: get with params + variables: + foo1: bar11 + foo2: bar21 + sum_v: "${SumTwoInt(1, 2)}" + request: + method: GET + url: /get + params: + foo1: $foo1 + foo2: $foo2 + sum_v: $sum_v + headers: + User-Agent: funplugin/${GetVersion()} + extract: + foo3: "body.args.foo2" + validate: + - eq: ["status_code", 200] + - eq: ["body.args.foo1", "bar11"] + - eq: ["body.args.sum_v", "3"] + - eq: ["body.args.foo2", "bar21"] +- + name: post raw text + variables: + foo1: "bar12" + foo3: "bar32" + request: + method: POST + url: /post + headers: + User-Agent: funplugin/${GetVersion()} + Content-Type: "text/plain" + data: "This is expected to be sent back as part of response body: $foo1-$foo2-$foo3." + validate: + - eq: ["status_code", 200] + - eq: ["body.data", "This is expected to be sent back as part of response body: bar12-$expect_foo2-bar32."] +- + name: post form data + variables: + foo2: bar23 + request: + method: POST + url: /post + headers: + User-Agent: funplugin/${GetVersion()} + Content-Type: "application/x-www-form-urlencoded" + data: "foo1=$foo1&foo2=$foo2&foo3=$foo3" + validate: + - eq: ["status_code", 200] + - eq: ["body.form.foo1", "$expect_foo1"] + - eq: ["body.form.foo2", "bar23"] + - eq: ["body.form.foo3", "bar21"] diff --git a/hrp/internal/scaffold/templates/testcases/demo_go_with_funplugin.json b/hrp/internal/scaffold/templates/testcases/demo_go_with_funplugin.json new file mode 100644 index 00000000..0af40519 --- /dev/null +++ b/hrp/internal/scaffold/templates/testcases/demo_go_with_funplugin.json @@ -0,0 +1,176 @@ +{ + "config": { + "name": "demo with complex mechanisms", + "base_url": "https://postman-echo.com", + "variables": { + "a": "${Sum(10, 2.3)}", + "b": 3.45, + "n": "${SumInts(1, 2, 2)}", + "varFoo1": "${gen_random_string($n)}", + "varFoo2": "${max($a, $b)}" + } + }, + "teststeps": [ + { + "name": "transaction 1 start", + "transaction": { + "name": "tran1", + "type": "start" + } + }, + { + "name": "get with params", + "request": { + "method": "GET", + "url": "/get", + "params": { + "foo1": "$varFoo1", + "foo2": "$varFoo2" + }, + "headers": { + "User-Agent": "HttpRunnerPlus" + } + }, + "variables": { + "b": 34.5, + "n": 3, + "name": "get with params", + "varFoo2": "${max($a, $b)}" + }, + "setup_hooks": [ + "${SetupHookExample($name)}" + ], + "teardown_hooks": [ + "${TeardownHookExample($name)}" + ], + "extract": { + "varFoo1": "body.args.foo1" + }, + "validate": [ + { + "check": "status_code", + "assert": "equals", + "expect": 200, + "msg": "check response status code" + }, + { + "check": "headers.\"Content-Type\"", + "assert": "startswith", + "expect": "application/json" + }, + { + "check": "body.args.foo1", + "assert": "length_equals", + "expect": 5, + "msg": "check args foo1" + }, + { + "check": "$varFoo1", + "assert": "length_equals", + "expect": 5, + "msg": "check args foo1" + }, + { + "check": "body.args.foo2", + "assert": "equals", + "expect": "34.5", + "msg": "check args foo2" + } + ] + }, + { + "name": "transaction 1 end", + "transaction": { + "name": "tran1", + "type": "end" + } + }, + { + "name": "post json data", + "request": { + "method": "POST", + "url": "/post", + "body": { + "foo1": "$varFoo1", + "foo2": "${max($a, $b)}" + } + }, + "validate": [ + { + "check": "status_code", + "assert": "equals", + "expect": 200, + "msg": "check status code" + }, + { + "check": "body.json.foo1", + "assert": "length_equals", + "expect": 5, + "msg": "check args foo1" + }, + { + "check": "body.json.foo2", + "assert": "equals", + "expect": 12.3, + "msg": "check args foo2" + } + ] + }, + { + "name": "post form data", + "request": { + "method": "POST", + "url": "/post", + "headers": { + "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8" + }, + "body": { + "foo1": "$varFoo1", + "foo2": "${max($a, $b)}", + "time": "${get_timestamp()}" + } + }, + "extract": { + "varTime": "body.form.time" + }, + "validate": [ + { + "check": "status_code", + "assert": "equals", + "expect": 200, + "msg": "check status code" + }, + { + "check": "body.form.foo1", + "assert": "length_equals", + "expect": 5, + "msg": "check args foo1" + }, + { + "check": "body.form.foo2", + "assert": "equals", + "expect": "12.3", + "msg": "check args foo2" + } + ] + }, + { + "name": "get with timestamp", + "request": { + "method": "GET", + "url": "/get", + "params": { + "time": "$varTime" + } + }, + "validate": [ + { + "check": "body.args.time", + "assert": "length_equals", + "expect": 13, + "msg": "check extracted var timestamp" + } + ] + } + ] +} \ No newline at end of file diff --git a/hrp/plugin.go b/hrp/plugin.go index 004fc1cb..72d6767d 100644 --- a/hrp/plugin.go +++ b/hrp/plugin.go @@ -2,9 +2,11 @@ package hrp import ( "fmt" + "github.com/httprunner/httprunner/v4/hrp/internal/build" "os" "os/signal" "path/filepath" + "strings" "syscall" "github.com/httprunner/funplugin" @@ -32,6 +34,21 @@ func initPlugin(path string, logOn bool) (plugin funplugin.IPlugin, pluginDir st // TODO: move pluginDir to funplugin pluginDir = filepath.Dir(pluginPath) + // compatible the format of debugtalk.py with v2/v3 + ext := filepath.Ext(pluginPath) + if ext == ".py" { + // skip if only debugtalk_gen.py exists + if !strings.HasSuffix(pluginPath, "debugtalk_gen.py") { + genPyPluginPath := filepath.Join(pluginDir, "debugtalk_gen.py") + err = build.Run(pluginPath, genPyPluginPath) + if err != nil { + log.Error().Err(err).Msgf(fmt.Sprintf("failed to build %s", pluginPath)) + return + } + pluginPath = genPyPluginPath + } + } + // found plugin file plugin, err = funplugin.Init(pluginPath, funplugin.WithLogOn(logOn)) if err != nil { @@ -62,19 +79,19 @@ func initPlugin(path string, logOn bool) (plugin funplugin.IPlugin, pluginDir st } func locatePlugin(path string) (pluginPath string, err error) { - // priority: hashicorp plugin (debugtalk.bin > debugtalk_gen.py > debugtalk.py) > go plugin (debugtalk.so) + // priority: hashicorp plugin (debugtalk.bin > debugtalk.py > debugtalk_gen.py) > go plugin (debugtalk.so) pluginPath, err = locateFile(path, hashicorpGoPluginFile) if err == nil { return } - pluginPath, err = locateFile(path, hashicorpPyPluginFile) + pluginPath, err = locateFile(path, debugtalkPyFile) if err == nil { return } - pluginPath, err = locateFile(path, debugtalkPyFile) + pluginPath, err = locateFile(path, hashicorpPyPluginFile) if err == nil { return } diff --git a/hrp/runner_test.go b/hrp/runner_test.go index d03f8d75..4c043090 100644 --- a/hrp/runner_test.go +++ b/hrp/runner_test.go @@ -8,14 +8,22 @@ import ( "github.com/rs/zerolog/log" "github.com/stretchr/testify/assert" - "github.com/httprunner/httprunner/v4/hrp/internal/builtin" + "github.com/httprunner/httprunner/v4/hrp/internal/build" "github.com/httprunner/httprunner/v4/hrp/internal/scaffold" ) +func buildHashicorpGoPluginWithNoFungo() { + log.Info().Msg("[init] build hashicorp go plugin") + err := build.Run(templatesDir+"noplugin/debugtalk.go", templatesDir+"debugtalk.bin") + if err != nil { + log.Error().Err(err).Msg("build hashicorp go plugin failed") + os.Exit(1) + } +} + func buildHashicorpGoPlugin() { log.Info().Msg("[init] build hashicorp go plugin") - err := builtin.ExecCommand("go", "build", - "-o", templatesDir+"debugtalk.bin", templatesDir+"plugin/debugtalk.go") + err := build.Run(templatesDir+"noplugin/debugtalk.go", templatesDir+"debugtalk.bin") if err != nil { log.Error().Err(err).Msg("build hashicorp go plugin failed") os.Exit(1) @@ -30,7 +38,7 @@ func removeHashicorpGoPlugin() { func buildHashicorpPyPlugin() { log.Info().Msg("[init] prepare hashicorp python plugin") pluginFile := templatesDir + "debugtalk.py" - err := scaffold.CopyFile("templates/plugin/debugtalk.py", pluginFile) + err := scaffold.CopyFile("templates/noplugin/debugtalk.py", pluginFile) if err != nil { log.Error().Err(err).Msg("build hashicorp python plugin failed") os.Exit(1) @@ -39,24 +47,14 @@ func buildHashicorpPyPlugin() { func removeHashicorpPyPlugin() { log.Info().Msg("[teardown] remove hashicorp python plugin") - os.Remove(templatesDir + "debugtalk.py") + // on v4.1^, running case will generate debugtalk_gen.py used by python plugin + os.Remove(templatesDir + "debugtalk_gen.py") } func TestRunCaseWithGoPlugin(t *testing.T) { buildHashicorpGoPlugin() defer removeHashicorpGoPlugin() - assertRunTestCases(t) -} - -func TestRunCaseWithPythonPlugin(t *testing.T) { - buildHashicorpPyPlugin() - defer removeHashicorpPyPlugin() - - assertRunTestCases(t) -} - -func assertRunTestCases(t *testing.T) { testcase1 := &TestCase{ Config: NewConfig("TestCase1"). SetBaseURL("http://httpbin.org"), @@ -83,7 +81,7 @@ func assertRunTestCases(t *testing.T) { }, }, ), - NewStep("testcase1-step4").CallRefCase(&demoTestCaseWithPluginJSONPath), + NewStep("testcase1-step4").CallRefCase(&demoTestCaseWithGoPluginJSONPath), }, } testcase2 := &TestCase{ @@ -98,6 +96,18 @@ func assertRunTestCases(t *testing.T) { } } +func TestRunCaseWithPythonPlugin(t *testing.T) { + buildHashicorpPyPlugin() + defer removeHashicorpPyPlugin() + + r := NewRunner(t) + r.SetPluginLogOn() + err := r.Run(&demoTestCaseWithPluginJSONPath) + if err != nil { + t.Fatalf("run testcase error: %v", err) + } +} + func TestRunCaseWithThinkTime(t *testing.T) { buildHashicorpGoPlugin() defer removeHashicorpGoPlugin() @@ -160,15 +170,15 @@ func TestRunCaseWithPluginJSON(t *testing.T) { buildHashicorpGoPlugin() defer removeHashicorpGoPlugin() - err := NewRunner(nil).Run(&demoTestCaseWithPluginJSONPath) // hrp.Run(testCase) + err := NewRunner(nil).Run(&demoTestCaseWithGoPluginJSONPath) // hrp.Run(testCase) if err != nil { t.Fatal() } } func TestRunCaseWithPluginYAML(t *testing.T) { - buildHashicorpGoPlugin() - defer removeHashicorpGoPlugin() + buildHashicorpPyPlugin() + defer removeHashicorpPyPlugin() err := NewRunner(nil).Run(&demoTestCaseWithPluginYAMLPath) // hrp.Run(testCase) if err != nil { diff --git a/hrp/testcase_test.go b/hrp/testcase_test.go index eb6a69ae..6564612e 100644 --- a/hrp/testcase_test.go +++ b/hrp/testcase_test.go @@ -15,6 +15,7 @@ const ( var ( demoTestCaseWithPluginJSONPath TestCasePath = templatesDir + "testcases/demo_with_funplugin.json" + demoTestCaseWithGoPluginJSONPath TestCasePath = templatesDir + "testcases/demo_go_with_funplugin.json" demoTestCaseWithPluginYAMLPath TestCasePath = templatesDir + "testcases/demo_with_funplugin.yaml" demoTestCaseWithoutPluginJSONPath TestCasePath = templatesDir + "testcases/demo_without_funplugin.json" demoTestCaseWithoutPluginYAMLPath TestCasePath = templatesDir + "testcases/demo_without_funplugin.yaml" From 4bb7d8e6199d96d5fe09d19d4c69e2e51babbcbd Mon Sep 17 00:00:00 2001 From: xucong053 <xucong053@outlook.com> Date: Fri, 27 May 2022 11:22:31 +0800 Subject: [PATCH 073/109] update docs --- docs/cmd/hrp_build.md | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 docs/cmd/hrp_build.md diff --git a/docs/cmd/hrp_build.md b/docs/cmd/hrp_build.md new file mode 100644 index 00000000..c60b3e3c --- /dev/null +++ b/docs/cmd/hrp_build.md @@ -0,0 +1,31 @@ +## hrp build + +build plugin for testing + +### Synopsis + +build python/go plugin for testing + +``` +hrp build $path ... [flags] +``` + +### Examples + +``` + $ hrp build plugin/debugtalk.go + $ hrp build plugin/debugtalk.py +``` + +### Options + +``` + -h, --help help for build + -o, --output string funplugin product output path, default: cwd +``` + +### SEE ALSO + +* [hrp](hrp.md) - Next-Generation API Testing Solution. + +###### Auto generated by spf13/cobra on 27-May-2022 From c87b9f9944180f97ce1f5ef03fa58e446b939e59 Mon Sep 17 00:00:00 2001 From: debugtalk <mail@debugtalk.com> Date: Fri, 27 May 2022 20:41:20 +0800 Subject: [PATCH 074/109] change: add user cases --- README.en.md | 6 ++++++ README.md | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/README.en.md b/README.en.md index ce40fffe..83ce83b9 100644 --- a/README.en.md +++ b/README.en.md @@ -104,6 +104,12 @@ Flags: Use "hrp [command] --help" for more information about a command. ``` +## User Cases + +<a href="https://httprunner.com/docs/cases/dji-ibg"><img src="https://httprunner.com/image/logo/dji.jpeg" title="大疆" link="" width="60"></a> +<a href="https://httprunner.com/docs/cases/bytedance-feishu"><img src="https://httprunner.com/image/logo/feishu.jpeg" title="飞书" width="60"></a> +<a href="https://httprunner.com/docs/cases/bytedance-douyin"><img src="https://httprunner.com/image/logo/douyin.jpg" title="抖音" width="60"></a> + ## Subscribe 关注 HttpRunner 的微信公众号,第一时间获得最新资讯。 diff --git a/README.md b/README.md index f8949730..887c2186 100644 --- a/README.md +++ b/README.md @@ -97,6 +97,12 @@ Flags: Use "hrp [command] --help" for more information about a command. ``` +## 用户案例 + +<a href="https://httprunner.com/docs/cases/dji-ibg"><img src="https://httprunner.com/image/logo/dji.jpeg" title="大疆" link="" width="60"></a> +<a href="https://httprunner.com/docs/cases/bytedance-feishu"><img src="https://httprunner.com/image/logo/feishu.jpeg" title="飞书" width="60"></a> +<a href="https://httprunner.com/docs/cases/bytedance-douyin"><img src="https://httprunner.com/image/logo/douyin.jpg" title="抖音" width="60"></a> + ## 赞助商 ### 金牌赞助商 From 605c0034ae7f309256adbf6fae7502ff99636ce5 Mon Sep 17 00:00:00 2001 From: debugtalk <mail@debugtalk.com> Date: Fri, 27 May 2022 20:52:42 +0800 Subject: [PATCH 075/109] change: update user cases --- README.en.md | 6 +++--- README.md | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/README.en.md b/README.en.md index 83ce83b9..df1bea1d 100644 --- a/README.en.md +++ b/README.en.md @@ -106,9 +106,9 @@ Use "hrp [command] --help" for more information about a command. ## User Cases -<a href="https://httprunner.com/docs/cases/dji-ibg"><img src="https://httprunner.com/image/logo/dji.jpeg" title="大疆" link="" width="60"></a> -<a href="https://httprunner.com/docs/cases/bytedance-feishu"><img src="https://httprunner.com/image/logo/feishu.jpeg" title="飞书" width="60"></a> -<a href="https://httprunner.com/docs/cases/bytedance-douyin"><img src="https://httprunner.com/image/logo/douyin.jpg" title="抖音" width="60"></a> +<a href="https://httprunner.com/docs/cases/dji-ibg"><img src="https://httprunner.com/image/logo/dji.jpeg" title="大疆 - 基于 HttpRunner 构建完整的自动化测试体系" width="60"></a> +<a href="https://httprunner.com/docs/cases/bytedance-feishu"><img src="https://httprunner.com/image/logo/feishu.jpeg" title="飞书 - 使用 HttpRunner 替换已有测试平台的执行引擎" width="60"></a> +<a href="https://httprunner.com/docs/cases/bytedance-douyin"><img src="https://httprunner.com/image/logo/douyin.jpg" title="抖音 - 使用 HttpRunner 实现开放平台动态加速效果评估" width="60"></a> ## Subscribe diff --git a/README.md b/README.md index 887c2186..f518a140 100644 --- a/README.md +++ b/README.md @@ -99,9 +99,9 @@ Use "hrp [command] --help" for more information about a command. ## 用户案例 -<a href="https://httprunner.com/docs/cases/dji-ibg"><img src="https://httprunner.com/image/logo/dji.jpeg" title="大疆" link="" width="60"></a> -<a href="https://httprunner.com/docs/cases/bytedance-feishu"><img src="https://httprunner.com/image/logo/feishu.jpeg" title="飞书" width="60"></a> -<a href="https://httprunner.com/docs/cases/bytedance-douyin"><img src="https://httprunner.com/image/logo/douyin.jpg" title="抖音" width="60"></a> +<a href="https://httprunner.com/docs/cases/dji-ibg"><img src="https://httprunner.com/image/logo/dji.jpeg" title="大疆 - 基于 HttpRunner 构建完整的自动化测试体系" width="60"></a> +<a href="https://httprunner.com/docs/cases/bytedance-feishu"><img src="https://httprunner.com/image/logo/feishu.jpeg" title="飞书 - 使用 HttpRunner 替换已有测试平台的执行引擎" width="60"></a> +<a href="https://httprunner.com/docs/cases/bytedance-douyin"><img src="https://httprunner.com/image/logo/douyin.jpg" title="抖音 - 使用 HttpRunner 实现开放平台动态加速效果评估" width="60"></a> ## 赞助商 From 08ff6fe5f574a7b7b1c3275ebd2cda82dd50950b Mon Sep 17 00:00:00 2001 From: xucong053 <xucong053@outlook.com> Date: Fri, 27 May 2022 16:40:17 +0800 Subject: [PATCH 076/109] fix: unittest --- docs/cmd/hrp.md | 4 +- docs/cmd/hrp_boom.md | 2 +- docs/cmd/hrp_build.md | 2 +- docs/cmd/hrp_convert.md | 2 +- docs/cmd/hrp_har2case.md | 2 +- docs/cmd/hrp_pytest.md | 2 +- docs/cmd/hrp_run.md | 2 +- docs/cmd/hrp_startproject.md | 2 +- docs/cmd/hrp_wiki.md | 2 +- .../demo-with-go-plugin/plugin/debugtalk.go | 14 +- examples/demo-with-go-plugin/plugin/go.mod | 2 +- examples/demo-with-go-plugin/plugin/go.sum | 4 +- examples/demo-with-go-plugin/proj.json | 2 +- .../demo-with-go-plugin/testcases/demo.json | 8 +- .../testcases/ref_testcase.yml | 2 +- .../testcases/requests.json | 8 +- .../testcases/requests.yml | 8 +- examples/demo-with-no-fungo/.gitignore | 15 -- examples/demo-with-no-fungo/har/.keep | 0 .../demo-with-no-fungo/testcases/demo.json | 176 ------------------ .../testcases/ref_testcase.yml | 33 ---- .../testcases/requests.json | 138 -------------- .../demo-with-no-fungo/testcases/requests.yml | 65 ------- examples/demo-with-no-funppy/.gitignore | 15 -- examples/demo-with-no-funppy/har/.keep | 0 .../demo-with-no-funppy/testcases/demo.json | 176 ------------------ .../testcases/ref_testcase.yml | 33 ---- .../testcases/requests.json | 138 -------------- .../testcases/requests.yml | 65 ------- examples/demo-with-py-plugin/proj.json | 2 +- examples/demo-without-plugin/proj.json | 2 +- go.mod | 2 +- go.sum | 4 +- hrp/boomer_test.go | 2 +- hrp/internal/build/main.go | 6 +- hrp/internal/build/main_test.go | 17 +- .../internal/build}/plugin/debugtalk.go | 10 +- .../internal/build/plugin}/debugtalk.py | 2 +- .../scaffold/templates/plugin/debugtalk.go | 14 +- .../testcases/demo_go_ref_testcase.yml | 33 ---- .../templates/testcases/demo_go_requests.json | 138 -------------- .../templates/testcases/demo_go_requests.yml | 65 ------- .../testcases/demo_go_with_funplugin.json | 176 ------------------ hrp/parser.go | 10 +- hrp/plugin.go | 3 +- hrp/runner_test.go | 44 ++--- hrp/testcase_test.go | 1 - 47 files changed, 87 insertions(+), 1366 deletions(-) delete mode 100644 examples/demo-with-no-fungo/.gitignore delete mode 100644 examples/demo-with-no-fungo/har/.keep delete mode 100644 examples/demo-with-no-fungo/testcases/demo.json delete mode 100644 examples/demo-with-no-fungo/testcases/ref_testcase.yml delete mode 100644 examples/demo-with-no-fungo/testcases/requests.json delete mode 100644 examples/demo-with-no-fungo/testcases/requests.yml delete mode 100644 examples/demo-with-no-funppy/.gitignore delete mode 100644 examples/demo-with-no-funppy/har/.keep delete mode 100644 examples/demo-with-no-funppy/testcases/demo.json delete mode 100644 examples/demo-with-no-funppy/testcases/ref_testcase.yml delete mode 100644 examples/demo-with-no-funppy/testcases/requests.json delete mode 100644 examples/demo-with-no-funppy/testcases/requests.yml rename {examples/demo-with-no-fungo => hrp/internal/build}/plugin/debugtalk.go (91%) rename {examples/demo-with-no-funppy => hrp/internal/build/plugin}/debugtalk.py (96%) delete mode 100644 hrp/internal/scaffold/templates/testcases/demo_go_ref_testcase.yml delete mode 100644 hrp/internal/scaffold/templates/testcases/demo_go_requests.json delete mode 100644 hrp/internal/scaffold/templates/testcases/demo_go_requests.yml delete mode 100644 hrp/internal/scaffold/templates/testcases/demo_go_with_funplugin.json diff --git a/docs/cmd/hrp.md b/docs/cmd/hrp.md index ce9ba71c..db7329f8 100644 --- a/docs/cmd/hrp.md +++ b/docs/cmd/hrp.md @@ -30,12 +30,12 @@ Copyright 2017 debugtalk ### SEE ALSO * [hrp boom](hrp_boom.md) - run load test with boomer -* [hrp convert](hrp_convert.md) - convert to JSON/YAML/gotest/pytest testcases * [hrp build](hrp_build.md) - build plugin for testing +* [hrp convert](hrp_convert.md) - convert to JSON/YAML/gotest/pytest testcases * [hrp har2case](hrp_har2case.md) - convert HAR to json/yaml testcase files * [hrp pytest](hrp_pytest.md) - run API test with pytest * [hrp run](hrp_run.md) - run API test with go engine * [hrp startproject](hrp_startproject.md) - create a scaffold project * [hrp wiki](hrp_wiki.md) - visit https://httprunner.com -###### Auto generated by spf13/cobra on 27-May-2022 +###### Auto generated by spf13/cobra on 28-May-2022 diff --git a/docs/cmd/hrp_boom.md b/docs/cmd/hrp_boom.md index 429a0ed3..6f30b821 100644 --- a/docs/cmd/hrp_boom.md +++ b/docs/cmd/hrp_boom.md @@ -42,4 +42,4 @@ hrp boom [flags] * [hrp](hrp.md) - Next-Generation API Testing Solution. -###### Auto generated by spf13/cobra on 27-May-2022 +###### Auto generated by spf13/cobra on 28-May-2022 diff --git a/docs/cmd/hrp_build.md b/docs/cmd/hrp_build.md index c60b3e3c..f3b222fe 100644 --- a/docs/cmd/hrp_build.md +++ b/docs/cmd/hrp_build.md @@ -28,4 +28,4 @@ hrp build $path ... [flags] * [hrp](hrp.md) - Next-Generation API Testing Solution. -###### Auto generated by spf13/cobra on 27-May-2022 +###### Auto generated by spf13/cobra on 28-May-2022 diff --git a/docs/cmd/hrp_convert.md b/docs/cmd/hrp_convert.md index 3083456c..c52a5348 100644 --- a/docs/cmd/hrp_convert.md +++ b/docs/cmd/hrp_convert.md @@ -22,4 +22,4 @@ hrp convert $path... [flags] * [hrp](hrp.md) - Next-Generation API Testing Solution. -###### Auto generated by spf13/cobra on 27-May-2022 +###### Auto generated by spf13/cobra on 28-May-2022 diff --git a/docs/cmd/hrp_har2case.md b/docs/cmd/hrp_har2case.md index 592c5281..2d4cd832 100644 --- a/docs/cmd/hrp_har2case.md +++ b/docs/cmd/hrp_har2case.md @@ -24,4 +24,4 @@ hrp har2case $har_path... [flags] * [hrp](hrp.md) - Next-Generation API Testing Solution. -###### Auto generated by spf13/cobra on 27-May-2022 +###### Auto generated by spf13/cobra on 28-May-2022 diff --git a/docs/cmd/hrp_pytest.md b/docs/cmd/hrp_pytest.md index 711c8bac..87c1e906 100644 --- a/docs/cmd/hrp_pytest.md +++ b/docs/cmd/hrp_pytest.md @@ -16,4 +16,4 @@ hrp pytest $path ... [flags] * [hrp](hrp.md) - Next-Generation API Testing Solution. -###### Auto generated by spf13/cobra on 27-May-2022 +###### Auto generated by spf13/cobra on 28-May-2022 diff --git a/docs/cmd/hrp_run.md b/docs/cmd/hrp_run.md index 63da347e..21a42988 100644 --- a/docs/cmd/hrp_run.md +++ b/docs/cmd/hrp_run.md @@ -35,4 +35,4 @@ hrp run $path... [flags] * [hrp](hrp.md) - Next-Generation API Testing Solution. -###### Auto generated by spf13/cobra on 27-May-2022 +###### Auto generated by spf13/cobra on 28-May-2022 diff --git a/docs/cmd/hrp_startproject.md b/docs/cmd/hrp_startproject.md index e55c5429..45c1fdb3 100644 --- a/docs/cmd/hrp_startproject.md +++ b/docs/cmd/hrp_startproject.md @@ -21,4 +21,4 @@ hrp startproject $project_name [flags] * [hrp](hrp.md) - Next-Generation API Testing Solution. -###### Auto generated by spf13/cobra on 27-May-2022 +###### Auto generated by spf13/cobra on 28-May-2022 diff --git a/docs/cmd/hrp_wiki.md b/docs/cmd/hrp_wiki.md index 2eecbdd0..16fdf14d 100644 --- a/docs/cmd/hrp_wiki.md +++ b/docs/cmd/hrp_wiki.md @@ -16,4 +16,4 @@ hrp wiki [flags] * [hrp](hrp.md) - Next-Generation API Testing Solution. -###### Auto generated by spf13/cobra on 27-May-2022 +###### Auto generated by spf13/cobra on 28-May-2022 diff --git a/examples/demo-with-go-plugin/plugin/debugtalk.go b/examples/demo-with-go-plugin/plugin/debugtalk.go index 3995ea24..b3b39400 100644 --- a/examples/demo-with-go-plugin/plugin/debugtalk.go +++ b/examples/demo-with-go-plugin/plugin/debugtalk.go @@ -46,12 +46,12 @@ func GetVersion() string { } func main() { - fungo.Register("GetVersion", GetVersion) - fungo.Register("SumInts", SumInts) - fungo.Register("SumTwoInt", SumTwoInt) - fungo.Register("SumTwoInt", SumTwoInt) - fungo.Register("Sum", Sum) - fungo.Register("SetupHookExample", SetupHookExample) - fungo.Register("TeardownHookExample", TeardownHookExample) + fungo.Register("get_version", GetVersion) + fungo.Register("sum_ints", SumInts) + fungo.Register("sum_two_int", SumTwoInt) + fungo.Register("sum_two", SumTwoInt) + fungo.Register("sum", Sum) + fungo.Register("setup_hook_example", SetupHookExample) + fungo.Register("teardown_hook_example", TeardownHookExample) fungo.Serve() } diff --git a/examples/demo-with-go-plugin/plugin/go.mod b/examples/demo-with-go-plugin/plugin/go.mod index 08a135d0..a36c1c27 100644 --- a/examples/demo-with-go-plugin/plugin/go.mod +++ b/examples/demo-with-go-plugin/plugin/go.mod @@ -2,4 +2,4 @@ module plugin go 1.16 -require github.com/httprunner/funplugin v0.4.6 // indirect +require github.com/httprunner/funplugin v0.4.7 // indirect diff --git a/examples/demo-with-go-plugin/plugin/go.sum b/examples/demo-with-go-plugin/plugin/go.sum index 59ea6478..bae78b47 100644 --- a/examples/demo-with-go-plugin/plugin/go.sum +++ b/examples/demo-with-go-plugin/plugin/go.sum @@ -58,8 +58,8 @@ github.com/hashicorp/go-plugin v1.4.3 h1:DXmvivbWD5qdiBts9TpBC7BYL1Aia5sxbRgQB+v github.com/hashicorp/go-plugin v1.4.3/go.mod h1:5fGEH17QVwTTcR0zV7yhDPLLmFX9YSZ38b18Udy6vYQ= github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb h1:b5rjCoWHc7eqmAS4/qyk21ZsHyb6Mxv/jykxvNTkU4M= github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= -github.com/httprunner/funplugin v0.4.6 h1:wwpjzo3G9a5BCXBkHs845w4ifKaCtVa/yQjREQjQOgo= -github.com/httprunner/funplugin v0.4.6/go.mod h1:vPyeJIfbpGe0epZZtAV0wCn16gLY9+imSw/zfxq0Lcc= +github.com/httprunner/funplugin v0.4.7 h1:bmk84BL8oPGE/rgxCuHgPcwJtBnwDzm/ocmFY/cKcos= +github.com/httprunner/funplugin v0.4.7/go.mod h1:vPyeJIfbpGe0epZZtAV0wCn16gLY9+imSw/zfxq0Lcc= github.com/jhump/protoreflect v1.6.0/go.mod h1:eaTn3RZAmMBcV0fifFvlm6VHNz3wSkYyXYWUh7ymB74= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= diff --git a/examples/demo-with-go-plugin/proj.json b/examples/demo-with-go-plugin/proj.json index 2b2fcb6b..6e7a483a 100644 --- a/examples/demo-with-go-plugin/proj.json +++ b/examples/demo-with-go-plugin/proj.json @@ -1,6 +1,6 @@ { "project_name": "demo-with-go-plugin", "project_path": "/Users/xxxxx/go/src/github.com/httprunner/httprunner/examples/demo-with-go-plugin", - "create_time": "2022-05-27T11:34:23.903959+08:00", + "create_time": "2022-05-28T02:00:18.084185+08:00", "hrp_version": "v4.1.0-beta" } \ No newline at end of file diff --git a/examples/demo-with-go-plugin/testcases/demo.json b/examples/demo-with-go-plugin/testcases/demo.json index 0af40519..1bb63ed8 100644 --- a/examples/demo-with-go-plugin/testcases/demo.json +++ b/examples/demo-with-go-plugin/testcases/demo.json @@ -3,9 +3,9 @@ "name": "demo with complex mechanisms", "base_url": "https://postman-echo.com", "variables": { - "a": "${Sum(10, 2.3)}", + "a": "${sum(10, 2.3)}", "b": 3.45, - "n": "${SumInts(1, 2, 2)}", + "n": "${sum_ints(1, 2, 2)}", "varFoo1": "${gen_random_string($n)}", "varFoo2": "${max($a, $b)}" } @@ -38,10 +38,10 @@ "varFoo2": "${max($a, $b)}" }, "setup_hooks": [ - "${SetupHookExample($name)}" + "${setup_hook_example($name)}" ], "teardown_hooks": [ - "${TeardownHookExample($name)}" + "${teardown_hook_example($name)}" ], "extract": { "varFoo1": "body.args.foo1" diff --git a/examples/demo-with-go-plugin/testcases/ref_testcase.yml b/examples/demo-with-go-plugin/testcases/ref_testcase.yml index 010133cf..0816481c 100644 --- a/examples/demo-with-go-plugin/testcases/ref_testcase.yml +++ b/examples/demo-with-go-plugin/testcases/ref_testcase.yml @@ -24,7 +24,7 @@ teststeps: method: POST url: /post headers: - User-Agent: funplugin/${GetVersion()} + User-Agent: funplugin/${get_version()} Content-Type: "application/x-www-form-urlencoded" body: "foo1=$foo1&foo2=$foo3" validate: diff --git a/examples/demo-with-go-plugin/testcases/requests.json b/examples/demo-with-go-plugin/testcases/requests.json index d4dcb276..162632b4 100644 --- a/examples/demo-with-go-plugin/testcases/requests.json +++ b/examples/demo-with-go-plugin/testcases/requests.json @@ -19,7 +19,7 @@ "variables": { "foo1": "${ENV(USERNAME)}", "foo2": "bar21", - "sum_v": "${SumTwoInt(1, 2)}" + "sum_v": "${sum_two_int(1, 2)}" }, "request": { "method": "GET", @@ -30,7 +30,7 @@ "sum_v": "$sum_v" }, "headers": { - "User-Agent": "funplugin/${GetVersion()}" + "User-Agent": "funplugin/${get_version()}" } }, "extract": { @@ -73,7 +73,7 @@ "method": "POST", "url": "/post", "headers": { - "User-Agent": "funplugin/${GetVersion()}", + "User-Agent": "funplugin/${get_version()}", "Content-Type": "text/plain" }, "body": "This is expected to be sent back as part of response body: $foo1-$foo2-$foo3." @@ -102,7 +102,7 @@ "method": "POST", "url": "/post", "headers": { - "User-Agent": "funplugin/${GetVersion()}", + "User-Agent": "funplugin/${get_version()}", "Content-Type": "application/x-www-form-urlencoded" }, "body": "foo1=$foo1&foo2=$foo2&foo3=$foo3" diff --git a/examples/demo-with-go-plugin/testcases/requests.yml b/examples/demo-with-go-plugin/testcases/requests.yml index add3a28d..034dbefb 100644 --- a/examples/demo-with-go-plugin/testcases/requests.yml +++ b/examples/demo-with-go-plugin/testcases/requests.yml @@ -14,7 +14,7 @@ teststeps: variables: foo1: ${ENV(USERNAME)} foo2: bar21 - sum_v: "${SumTwoInt(1, 2)}" + sum_v: "${sum_two_int(1, 2)}" request: method: GET url: $base_url/get @@ -23,7 +23,7 @@ teststeps: foo2: $foo2 sum_v: $sum_v headers: - User-Agent: funplugin/${GetVersion()} + User-Agent: funplugin/${get_version()} extract: foo3: "body.args.foo2" validate: @@ -40,7 +40,7 @@ teststeps: method: POST url: $base_url/post headers: - User-Agent: funplugin/${GetVersion()} + User-Agent: funplugin/${get_version()} Content-Type: "text/plain" body: "This is expected to be sent back as part of response body: $foo1-$foo2-$foo3." validate: @@ -54,7 +54,7 @@ teststeps: method: POST url: $base_url/post headers: - User-Agent: funplugin/${GetVersion()} + User-Agent: funplugin/${get_version()} Content-Type: "application/x-www-form-urlencoded" body: "foo1=$foo1&foo2=$foo2&foo3=$foo3" validate: diff --git a/examples/demo-with-no-fungo/.gitignore b/examples/demo-with-no-fungo/.gitignore deleted file mode 100644 index 33401380..00000000 --- a/examples/demo-with-no-fungo/.gitignore +++ /dev/null @@ -1,15 +0,0 @@ -.env -reports/ -*.so -.vscode/ -.idea/ -.DS_Store -output/ -__pycache__/ -*.pyc -.python-version -logs/ - -# plugin -debugtalk.bin -debugtalk.so diff --git a/examples/demo-with-no-fungo/har/.keep b/examples/demo-with-no-fungo/har/.keep deleted file mode 100644 index e69de29b..00000000 diff --git a/examples/demo-with-no-fungo/testcases/demo.json b/examples/demo-with-no-fungo/testcases/demo.json deleted file mode 100644 index a127d26d..00000000 --- a/examples/demo-with-no-fungo/testcases/demo.json +++ /dev/null @@ -1,176 +0,0 @@ -{ - "config": { - "name": "demo with complex mechanisms", - "base_url": "https://postman-echo.com", - "variables": { - "a": "${Sum(10, 2.3)}", - "b": 3.45, - "n": "${SumInts(1, 2, 2)}", - "varFoo1": "${GenRandomString($n)}", - "varFoo2": "${Max($a, $b)}" - } - }, - "teststeps": [ - { - "name": "transaction 1 start", - "transaction": { - "name": "tran1", - "type": "start" - } - }, - { - "name": "get with params", - "request": { - "method": "GET", - "url": "/get", - "params": { - "foo1": "$varFoo1", - "foo2": "$varFoo2" - }, - "headers": { - "User-Agent": "HttpRunnerPlus" - } - }, - "variables": { - "b": 34.5, - "n": 3, - "name": "get with params", - "varFoo2": "${Max($a, $b)}" - }, - "setup_hooks": [ - "${SetupHookExample($name)}" - ], - "teardown_hooks": [ - "${TeardownHookExample($name)}" - ], - "extract": { - "varFoo1": "body.args.foo1" - }, - "validate": [ - { - "check": "status_code", - "assert": "equals", - "expect": 200, - "msg": "check response status code" - }, - { - "check": "headers.\"Content-Type\"", - "assert": "startswith", - "expect": "application/json" - }, - { - "check": "body.args.foo1", - "assert": "length_equals", - "expect": 5, - "msg": "check args foo1" - }, - { - "check": "$varFoo1", - "assert": "length_equals", - "expect": 5, - "msg": "check args foo1" - }, - { - "check": "body.args.foo2", - "assert": "equals", - "expect": "34.5", - "msg": "check args foo2" - } - ] - }, - { - "name": "transaction 1 end", - "transaction": { - "name": "tran1", - "type": "end" - } - }, - { - "name": "post json data", - "request": { - "method": "POST", - "url": "/post", - "body": { - "foo1": "$varFoo1", - "foo2": "${Max($a, $b)}" - } - }, - "validate": [ - { - "check": "status_code", - "assert": "equals", - "expect": 200, - "msg": "check status code" - }, - { - "check": "body.json.foo1", - "assert": "length_equals", - "expect": 5, - "msg": "check args foo1" - }, - { - "check": "body.json.foo2", - "assert": "equals", - "expect": 12.3, - "msg": "check args foo2" - } - ] - }, - { - "name": "post form data", - "request": { - "method": "POST", - "url": "/post", - "headers": { - "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8" - }, - "body": { - "foo1": "$varFoo1", - "foo2": "${Max($a, $b)}", - "time": "${GetTimestamp()}" - } - }, - "extract": { - "varTime": "body.form.time" - }, - "validate": [ - { - "check": "status_code", - "assert": "equals", - "expect": 200, - "msg": "check status code" - }, - { - "check": "body.form.foo1", - "assert": "length_equals", - "expect": 5, - "msg": "check args foo1" - }, - { - "check": "body.form.foo2", - "assert": "equals", - "expect": "12.3", - "msg": "check args foo2" - } - ] - }, - { - "name": "get with timestamp", - "request": { - "method": "GET", - "url": "/get", - "params": { - "time": "$varTime" - } - }, - "validate": [ - { - "check": "body.args.time", - "assert": "length_equals", - "expect": 13, - "msg": "check extracted var timestamp" - } - ] - } - ] -} \ No newline at end of file diff --git a/examples/demo-with-no-fungo/testcases/ref_testcase.yml b/examples/demo-with-no-fungo/testcases/ref_testcase.yml deleted file mode 100644 index 957dbdd4..00000000 --- a/examples/demo-with-no-fungo/testcases/ref_testcase.yml +++ /dev/null @@ -1,33 +0,0 @@ -config: - name: "request methods testcase: reference testcase" - variables: - foo1: testsuite_config_bar1 - expect_foo1: testsuite_config_bar1 - expect_foo2: config_bar2 - base_url: "https://postman-echo.com" - verify: False - -teststeps: -- - name: request with functions - variables: - foo1: testcase_ref_bar1 - expect_foo1: testcase_ref_bar1 - testcase: testcases/requests.yml - export: - - foo3 -- - name: post form data - variables: - foo1: bar1 - request: - method: POST - url: /post - headers: - User-Agent: funplugin/${GetVersion()} - Content-Type: "application/x-www-form-urlencoded" - data: "foo1=$foo1&foo2=$foo3" - validate: - - eq: ["status_code", 200] - - eq: ["body.form.foo1", "bar1"] - - eq: ["body.form.foo2", "bar21"] diff --git a/examples/demo-with-no-fungo/testcases/requests.json b/examples/demo-with-no-fungo/testcases/requests.json deleted file mode 100644 index f54e6340..00000000 --- a/examples/demo-with-no-fungo/testcases/requests.json +++ /dev/null @@ -1,138 +0,0 @@ -{ - "config": { - "name": "request methods testcase with functions", - "variables": { - "foo1": "config_bar1", - "foo2": "config_bar2", - "expect_foo1": "config_bar1", - "expect_foo2": "config_bar2" - }, - "base_url": "https://postman-echo.com", - "verify": false, - "export": [ - "foo3" - ] - }, - "teststeps": [ - { - "name": "get with params", - "variables": { - "foo1": "bar11", - "foo2": "bar21", - "sum_v": "${SumTwoInt(1, 2)}" - }, - "request": { - "method": "GET", - "url": "/get", - "params": { - "foo1": "$foo1", - "foo2": "$foo2", - "sum_v": "$sum_v" - }, - "headers": { - "User-Agent": "funplugin/${GetVersion()}" - } - }, - "extract": { - "foo3": "body.args.foo2" - }, - "validate": [ - { - "eq": [ - "status_code", - 200 - ] - }, - { - "eq": [ - "body.args.foo1", - "bar11" - ] - }, - { - "eq": [ - "body.args.sum_v", - "3" - ] - }, - { - "eq": [ - "body.args.foo2", - "bar21" - ] - } - ] - }, - { - "name": "post raw text", - "variables": { - "foo1": "bar12", - "foo3": "bar32" - }, - "request": { - "method": "POST", - "url": "/post", - "headers": { - "User-Agent": "funplugin/${GetVersion()}", - "Content-Type": "text/plain" - }, - "data": "This is expected to be sent back as part of response body: $foo1-$foo2-$foo3." - }, - "validate": [ - { - "eq": [ - "status_code", - 200 - ] - }, - { - "eq": [ - "body.data", - "This is expected to be sent back as part of response body: bar12-$expect_foo2-bar32." - ] - } - ] - }, - { - "name": "post form data", - "variables": { - "foo2": "bar23" - }, - "request": { - "method": "POST", - "url": "/post", - "headers": { - "User-Agent": "funplugin/${GetVersion()}", - "Content-Type": "application/x-www-form-urlencoded" - }, - "data": "foo1=$foo1&foo2=$foo2&foo3=$foo3" - }, - "validate": [ - { - "eq": [ - "status_code", - 200 - ] - }, - { - "eq": [ - "body.form.foo1", - "$expect_foo1" - ] - }, - { - "eq": [ - "body.form.foo2", - "bar23" - ] - }, - { - "eq": [ - "body.form.foo3", - "bar21" - ] - } - ] - } - ] -} \ No newline at end of file diff --git a/examples/demo-with-no-fungo/testcases/requests.yml b/examples/demo-with-no-fungo/testcases/requests.yml deleted file mode 100644 index 7884588f..00000000 --- a/examples/demo-with-no-fungo/testcases/requests.yml +++ /dev/null @@ -1,65 +0,0 @@ -config: - name: "request methods testcase with functions" - variables: - foo1: config_bar1 - foo2: config_bar2 - expect_foo1: config_bar1 - expect_foo2: config_bar2 - base_url: "https://postman-echo.com" - verify: False - export: ["foo3"] - -teststeps: -- - name: get with params - variables: - foo1: bar11 - foo2: bar21 - sum_v: "${SumTwoInt(1, 2)}" - request: - method: GET - url: /get - params: - foo1: $foo1 - foo2: $foo2 - sum_v: $sum_v - headers: - User-Agent: funplugin/${GetVersion()} - extract: - foo3: "body.args.foo2" - validate: - - eq: ["status_code", 200] - - eq: ["body.args.foo1", "bar11"] - - eq: ["body.args.sum_v", "3"] - - eq: ["body.args.foo2", "bar21"] -- - name: post raw text - variables: - foo1: "bar12" - foo3: "bar32" - request: - method: POST - url: /post - headers: - User-Agent: funplugin/${GetVersion()} - Content-Type: "text/plain" - data: "This is expected to be sent back as part of response body: $foo1-$foo2-$foo3." - validate: - - eq: ["status_code", 200] - - eq: ["body.data", "This is expected to be sent back as part of response body: bar12-$expect_foo2-bar32."] -- - name: post form data - variables: - foo2: bar23 - request: - method: POST - url: /post - headers: - User-Agent: funplugin/${GetVersion()} - Content-Type: "application/x-www-form-urlencoded" - data: "foo1=$foo1&foo2=$foo2&foo3=$foo3" - validate: - - eq: ["status_code", 200] - - eq: ["body.form.foo1", "$expect_foo1"] - - eq: ["body.form.foo2", "bar23"] - - eq: ["body.form.foo3", "bar21"] diff --git a/examples/demo-with-no-funppy/.gitignore b/examples/demo-with-no-funppy/.gitignore deleted file mode 100644 index 33401380..00000000 --- a/examples/demo-with-no-funppy/.gitignore +++ /dev/null @@ -1,15 +0,0 @@ -.env -reports/ -*.so -.vscode/ -.idea/ -.DS_Store -output/ -__pycache__/ -*.pyc -.python-version -logs/ - -# plugin -debugtalk.bin -debugtalk.so diff --git a/examples/demo-with-no-funppy/har/.keep b/examples/demo-with-no-funppy/har/.keep deleted file mode 100644 index e69de29b..00000000 diff --git a/examples/demo-with-no-funppy/testcases/demo.json b/examples/demo-with-no-funppy/testcases/demo.json deleted file mode 100644 index 1bb63ed8..00000000 --- a/examples/demo-with-no-funppy/testcases/demo.json +++ /dev/null @@ -1,176 +0,0 @@ -{ - "config": { - "name": "demo with complex mechanisms", - "base_url": "https://postman-echo.com", - "variables": { - "a": "${sum(10, 2.3)}", - "b": 3.45, - "n": "${sum_ints(1, 2, 2)}", - "varFoo1": "${gen_random_string($n)}", - "varFoo2": "${max($a, $b)}" - } - }, - "teststeps": [ - { - "name": "transaction 1 start", - "transaction": { - "name": "tran1", - "type": "start" - } - }, - { - "name": "get with params", - "request": { - "method": "GET", - "url": "/get", - "params": { - "foo1": "$varFoo1", - "foo2": "$varFoo2" - }, - "headers": { - "User-Agent": "HttpRunnerPlus" - } - }, - "variables": { - "b": 34.5, - "n": 3, - "name": "get with params", - "varFoo2": "${max($a, $b)}" - }, - "setup_hooks": [ - "${setup_hook_example($name)}" - ], - "teardown_hooks": [ - "${teardown_hook_example($name)}" - ], - "extract": { - "varFoo1": "body.args.foo1" - }, - "validate": [ - { - "check": "status_code", - "assert": "equals", - "expect": 200, - "msg": "check response status code" - }, - { - "check": "headers.\"Content-Type\"", - "assert": "startswith", - "expect": "application/json" - }, - { - "check": "body.args.foo1", - "assert": "length_equals", - "expect": 5, - "msg": "check args foo1" - }, - { - "check": "$varFoo1", - "assert": "length_equals", - "expect": 5, - "msg": "check args foo1" - }, - { - "check": "body.args.foo2", - "assert": "equals", - "expect": "34.5", - "msg": "check args foo2" - } - ] - }, - { - "name": "transaction 1 end", - "transaction": { - "name": "tran1", - "type": "end" - } - }, - { - "name": "post json data", - "request": { - "method": "POST", - "url": "/post", - "body": { - "foo1": "$varFoo1", - "foo2": "${max($a, $b)}" - } - }, - "validate": [ - { - "check": "status_code", - "assert": "equals", - "expect": 200, - "msg": "check status code" - }, - { - "check": "body.json.foo1", - "assert": "length_equals", - "expect": 5, - "msg": "check args foo1" - }, - { - "check": "body.json.foo2", - "assert": "equals", - "expect": 12.3, - "msg": "check args foo2" - } - ] - }, - { - "name": "post form data", - "request": { - "method": "POST", - "url": "/post", - "headers": { - "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8" - }, - "body": { - "foo1": "$varFoo1", - "foo2": "${max($a, $b)}", - "time": "${get_timestamp()}" - } - }, - "extract": { - "varTime": "body.form.time" - }, - "validate": [ - { - "check": "status_code", - "assert": "equals", - "expect": 200, - "msg": "check status code" - }, - { - "check": "body.form.foo1", - "assert": "length_equals", - "expect": 5, - "msg": "check args foo1" - }, - { - "check": "body.form.foo2", - "assert": "equals", - "expect": "12.3", - "msg": "check args foo2" - } - ] - }, - { - "name": "get with timestamp", - "request": { - "method": "GET", - "url": "/get", - "params": { - "time": "$varTime" - } - }, - "validate": [ - { - "check": "body.args.time", - "assert": "length_equals", - "expect": 13, - "msg": "check extracted var timestamp" - } - ] - } - ] -} \ No newline at end of file diff --git a/examples/demo-with-no-funppy/testcases/ref_testcase.yml b/examples/demo-with-no-funppy/testcases/ref_testcase.yml deleted file mode 100644 index 6cf32323..00000000 --- a/examples/demo-with-no-funppy/testcases/ref_testcase.yml +++ /dev/null @@ -1,33 +0,0 @@ -config: - name: "request methods testcase: reference testcase" - variables: - foo1: testsuite_config_bar1 - expect_foo1: testsuite_config_bar1 - expect_foo2: config_bar2 - base_url: "https://postman-echo.com" - verify: False - -teststeps: -- - name: request with functions - variables: - foo1: testcase_ref_bar1 - expect_foo1: testcase_ref_bar1 - testcase: testcases/requests.yml - export: - - foo3 -- - name: post form data - variables: - foo1: bar1 - request: - method: POST - url: /post - headers: - User-Agent: funplugin/${get_version()} - Content-Type: "application/x-www-form-urlencoded" - data: "foo1=$foo1&foo2=$foo3" - validate: - - eq: ["status_code", 200] - - eq: ["body.form.foo1", "bar1"] - - eq: ["body.form.foo2", "bar21"] diff --git a/examples/demo-with-no-funppy/testcases/requests.json b/examples/demo-with-no-funppy/testcases/requests.json deleted file mode 100644 index b13f3837..00000000 --- a/examples/demo-with-no-funppy/testcases/requests.json +++ /dev/null @@ -1,138 +0,0 @@ -{ - "config": { - "name": "request methods testcase with functions", - "variables": { - "foo1": "config_bar1", - "foo2": "config_bar2", - "expect_foo1": "config_bar1", - "expect_foo2": "config_bar2" - }, - "base_url": "https://postman-echo.com", - "verify": false, - "export": [ - "foo3" - ] - }, - "teststeps": [ - { - "name": "get with params", - "variables": { - "foo1": "bar11", - "foo2": "bar21", - "sum_v": "${sum_two_int(1, 2)}" - }, - "request": { - "method": "GET", - "url": "/get", - "params": { - "foo1": "$foo1", - "foo2": "$foo2", - "sum_v": "$sum_v" - }, - "headers": { - "User-Agent": "funplugin/${get_version()}" - } - }, - "extract": { - "foo3": "body.args.foo2" - }, - "validate": [ - { - "eq": [ - "status_code", - 200 - ] - }, - { - "eq": [ - "body.args.foo1", - "bar11" - ] - }, - { - "eq": [ - "body.args.sum_v", - "3" - ] - }, - { - "eq": [ - "body.args.foo2", - "bar21" - ] - } - ] - }, - { - "name": "post raw text", - "variables": { - "foo1": "bar12", - "foo3": "bar32" - }, - "request": { - "method": "POST", - "url": "/post", - "headers": { - "User-Agent": "funplugin/${get_version()}", - "Content-Type": "text/plain" - }, - "data": "This is expected to be sent back as part of response body: $foo1-$foo2-$foo3." - }, - "validate": [ - { - "eq": [ - "status_code", - 200 - ] - }, - { - "eq": [ - "body.data", - "This is expected to be sent back as part of response body: bar12-$expect_foo2-bar32." - ] - } - ] - }, - { - "name": "post form data", - "variables": { - "foo2": "bar23" - }, - "request": { - "method": "POST", - "url": "/post", - "headers": { - "User-Agent": "funplugin/${get_version()}", - "Content-Type": "application/x-www-form-urlencoded" - }, - "data": "foo1=$foo1&foo2=$foo2&foo3=$foo3" - }, - "validate": [ - { - "eq": [ - "status_code", - 200 - ] - }, - { - "eq": [ - "body.form.foo1", - "$expect_foo1" - ] - }, - { - "eq": [ - "body.form.foo2", - "bar23" - ] - }, - { - "eq": [ - "body.form.foo3", - "bar21" - ] - } - ] - } - ] -} \ No newline at end of file diff --git a/examples/demo-with-no-funppy/testcases/requests.yml b/examples/demo-with-no-funppy/testcases/requests.yml deleted file mode 100644 index 86d1b9cc..00000000 --- a/examples/demo-with-no-funppy/testcases/requests.yml +++ /dev/null @@ -1,65 +0,0 @@ -config: - name: "request methods testcase with functions" - variables: - foo1: config_bar1 - foo2: config_bar2 - expect_foo1: config_bar1 - expect_foo2: config_bar2 - base_url: "https://postman-echo.com" - verify: False - export: ["foo3"] - -teststeps: -- - name: get with params - variables: - foo1: bar11 - foo2: bar21 - sum_v: "${sum_two_int(1, 2)}" - request: - method: GET - url: /get - params: - foo1: $foo1 - foo2: $foo2 - sum_v: $sum_v - headers: - User-Agent: funplugin/${get_version()} - extract: - foo3: "body.args.foo2" - validate: - - eq: ["status_code", 200] - - eq: ["body.args.foo1", "bar11"] - - eq: ["body.args.sum_v", "3"] - - eq: ["body.args.foo2", "bar21"] -- - name: post raw text - variables: - foo1: "bar12" - foo3: "bar32" - request: - method: POST - url: /post - headers: - User-Agent: funplugin/${get_version()} - Content-Type: "text/plain" - data: "This is expected to be sent back as part of response body: $foo1-$foo2-$foo3." - validate: - - eq: ["status_code", 200] - - eq: ["body.data", "This is expected to be sent back as part of response body: bar12-$expect_foo2-bar32."] -- - name: post form data - variables: - foo2: bar23 - request: - method: POST - url: /post - headers: - User-Agent: funplugin/${get_version()} - Content-Type: "application/x-www-form-urlencoded" - data: "foo1=$foo1&foo2=$foo2&foo3=$foo3" - validate: - - eq: ["status_code", 200] - - eq: ["body.form.foo1", "$expect_foo1"] - - eq: ["body.form.foo2", "bar23"] - - eq: ["body.form.foo3", "bar21"] diff --git a/examples/demo-with-py-plugin/proj.json b/examples/demo-with-py-plugin/proj.json index 555bccd7..9d8cce0e 100644 --- a/examples/demo-with-py-plugin/proj.json +++ b/examples/demo-with-py-plugin/proj.json @@ -1,6 +1,6 @@ { "project_name": "demo-with-py-plugin", "project_path": "/Users/xxxxx/go/src/github.com/httprunner/httprunner/examples/demo-with-py-plugin", - "create_time": "2022-05-27T11:34:31.852589+08:00", + "create_time": "2022-05-28T02:00:28.517914+08:00", "hrp_version": "v4.1.0-beta" } \ No newline at end of file diff --git a/examples/demo-without-plugin/proj.json b/examples/demo-without-plugin/proj.json index 72c78cbf..e622b2a7 100644 --- a/examples/demo-without-plugin/proj.json +++ b/examples/demo-without-plugin/proj.json @@ -1,6 +1,6 @@ { "project_name": "demo-without-plugin", "project_path": "/Users/xxxxx/go/src/github.com/httprunner/httprunner/examples/demo-without-plugin", - "create_time": "2022-05-27T11:34:32.548637+08:00", + "create_time": "2022-05-28T02:00:29.191678+08:00", "hrp_version": "v4.1.0-beta" } \ No newline at end of file diff --git a/go.mod b/go.mod index 857c9444..080c56a0 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,7 @@ require ( github.com/go-openapi/spec v0.20.6 github.com/google/uuid v1.3.0 github.com/gorilla/websocket v1.4.1 - github.com/httprunner/funplugin v0.4.6 + github.com/httprunner/funplugin v0.4.7 github.com/jinzhu/copier v0.3.2 github.com/jmespath/go-jmespath v0.4.0 github.com/json-iterator/go v1.1.12 diff --git a/go.sum b/go.sum index 4651a6e7..921c4995 100644 --- a/go.sum +++ b/go.sum @@ -253,8 +253,8 @@ github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2p github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb h1:b5rjCoWHc7eqmAS4/qyk21ZsHyb6Mxv/jykxvNTkU4M= github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= -github.com/httprunner/funplugin v0.4.6 h1:wwpjzo3G9a5BCXBkHs845w4ifKaCtVa/yQjREQjQOgo= -github.com/httprunner/funplugin v0.4.6/go.mod h1:vPyeJIfbpGe0epZZtAV0wCn16gLY9+imSw/zfxq0Lcc= +github.com/httprunner/funplugin v0.4.7 h1:bmk84BL8oPGE/rgxCuHgPcwJtBnwDzm/ocmFY/cKcos= +github.com/httprunner/funplugin v0.4.7/go.mod h1:vPyeJIfbpGe0epZZtAV0wCn16gLY9+imSw/zfxq0Lcc= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/imkira/go-interpol v1.1.0/go.mod h1:z0h2/2T3XF8kyEPpRgJ3kmNv+C43p+I/CoI+jC3w2iA= diff --git a/hrp/boomer_test.go b/hrp/boomer_test.go index b70fc832..4edefa38 100644 --- a/hrp/boomer_test.go +++ b/hrp/boomer_test.go @@ -25,7 +25,7 @@ func TestBoomerStandaloneRun(t *testing.T) { NewStep("TestCase3").CallRefCase(&TestCase{Config: NewConfig("TestCase3")}), }, } - testcase2 := &demoTestCaseWithGoPluginJSONPath + testcase2 := &demoTestCaseWithPluginJSONPath b := NewBoomer(2, 1) go b.Run(testcase1, testcase2) diff --git a/hrp/internal/build/main.go b/hrp/internal/build/main.go index 39c2e92a..2c9cd28b 100644 --- a/hrp/internal/build/main.go +++ b/hrp/internal/build/main.go @@ -23,10 +23,10 @@ const ( funppy = `import funppy` fungo = `"github.com/httprunner/funplugin/fungo"` regexPythonFunctionName = `def ([a-zA-Z_]\w*)\(.*\)` - regexGoImports = `import\s*\(\n([\s\S]*)\n\)` - regexGoImport = `import\s*(\"[\s\S]*\")\n` + regexGoImports = `import \(([\s\S]*?)\)` + regexGoImport = `import (\"[\s\S]*\")` regexGoFunctionName = `func ([A-Z][a-zA-Z_]\w*)\(.*\)` - regexGoFunctionContent = `func [\s\S]*?\n}\n` + regexGoFunctionContent = `func [\s\S]*?\n}` ) //go:embed templates/debugtalkPythonTemplate diff --git a/hrp/internal/build/main_test.go b/hrp/internal/build/main_test.go index b26109a2..41eb1489 100644 --- a/hrp/internal/build/main_test.go +++ b/hrp/internal/build/main_test.go @@ -1,30 +1,21 @@ package build import ( - "github.com/httprunner/httprunner/v4/hrp/internal/builtin" "regexp" "testing" + "github.com/httprunner/httprunner/v4/hrp/internal/builtin" + "github.com/stretchr/testify/assert" ) func TestRun(t *testing.T) { - err := Run("../../../examples/demo-with-no-fungo/plugin/debugtalk.go", "") + err := Run("plugin/debugtalk.go", "./debugtalk_gen.bin") if !assert.Nil(t, err) { t.Fatal() } - err = Run("../../../examples/demo-with-no-funppy/debugtalk.py", "") - if !assert.Nil(t, err) { - t.Fatal() - } - - err = Run("../../../examples/demo-with-no-fungo/plugin/debugtalk.go", "./debugtalk_gen.bin") - if !assert.Nil(t, err) { - t.Fatal() - } - - err = Run("../../../examples/demo-with-no-funppy/debugtalk.py", "./debugtalk_gen.py") + err = Run("plugin/debugtalk.py", "./debugtalk_gen.py") if !assert.Nil(t, err) { t.Fatal() } diff --git a/examples/demo-with-no-fungo/plugin/debugtalk.go b/hrp/internal/build/plugin/debugtalk.go similarity index 91% rename from examples/demo-with-no-fungo/plugin/debugtalk.go rename to hrp/internal/build/plugin/debugtalk.go index 5810e0e4..eb189472 100644 --- a/examples/demo-with-no-fungo/plugin/debugtalk.go +++ b/hrp/internal/build/plugin/debugtalk.go @@ -1,13 +1,9 @@ -package main +package noplugin import ( "fmt" ) -func init() { - fmt.Println("init") -} - func SumTwoInt(a, b int) int { return a + b } @@ -42,3 +38,7 @@ func SetupHookExample(args string) string { func TeardownHookExample(args string) string { return fmt.Sprintf("step name: %v, teardown...", args) } + +func GetVersion() string { + return "v0.4" +} diff --git a/examples/demo-with-no-funppy/debugtalk.py b/hrp/internal/build/plugin/debugtalk.py similarity index 96% rename from examples/demo-with-no-funppy/debugtalk.py rename to hrp/internal/build/plugin/debugtalk.py index 8d93ae1f..9f4c52bc 100644 --- a/examples/demo-with-no-funppy/debugtalk.py +++ b/hrp/internal/build/plugin/debugtalk.py @@ -4,7 +4,7 @@ from typing import List def get_version(): - return "httprunner v4.0" + return "v0.4" def sleep(n_secs): diff --git a/hrp/internal/scaffold/templates/plugin/debugtalk.go b/hrp/internal/scaffold/templates/plugin/debugtalk.go index 3995ea24..b3b39400 100644 --- a/hrp/internal/scaffold/templates/plugin/debugtalk.go +++ b/hrp/internal/scaffold/templates/plugin/debugtalk.go @@ -46,12 +46,12 @@ func GetVersion() string { } func main() { - fungo.Register("GetVersion", GetVersion) - fungo.Register("SumInts", SumInts) - fungo.Register("SumTwoInt", SumTwoInt) - fungo.Register("SumTwoInt", SumTwoInt) - fungo.Register("Sum", Sum) - fungo.Register("SetupHookExample", SetupHookExample) - fungo.Register("TeardownHookExample", TeardownHookExample) + fungo.Register("get_version", GetVersion) + fungo.Register("sum_ints", SumInts) + fungo.Register("sum_two_int", SumTwoInt) + fungo.Register("sum_two", SumTwoInt) + fungo.Register("sum", Sum) + fungo.Register("setup_hook_example", SetupHookExample) + fungo.Register("teardown_hook_example", TeardownHookExample) fungo.Serve() } diff --git a/hrp/internal/scaffold/templates/testcases/demo_go_ref_testcase.yml b/hrp/internal/scaffold/templates/testcases/demo_go_ref_testcase.yml deleted file mode 100644 index 957dbdd4..00000000 --- a/hrp/internal/scaffold/templates/testcases/demo_go_ref_testcase.yml +++ /dev/null @@ -1,33 +0,0 @@ -config: - name: "request methods testcase: reference testcase" - variables: - foo1: testsuite_config_bar1 - expect_foo1: testsuite_config_bar1 - expect_foo2: config_bar2 - base_url: "https://postman-echo.com" - verify: False - -teststeps: -- - name: request with functions - variables: - foo1: testcase_ref_bar1 - expect_foo1: testcase_ref_bar1 - testcase: testcases/requests.yml - export: - - foo3 -- - name: post form data - variables: - foo1: bar1 - request: - method: POST - url: /post - headers: - User-Agent: funplugin/${GetVersion()} - Content-Type: "application/x-www-form-urlencoded" - data: "foo1=$foo1&foo2=$foo3" - validate: - - eq: ["status_code", 200] - - eq: ["body.form.foo1", "bar1"] - - eq: ["body.form.foo2", "bar21"] diff --git a/hrp/internal/scaffold/templates/testcases/demo_go_requests.json b/hrp/internal/scaffold/templates/testcases/demo_go_requests.json deleted file mode 100644 index f54e6340..00000000 --- a/hrp/internal/scaffold/templates/testcases/demo_go_requests.json +++ /dev/null @@ -1,138 +0,0 @@ -{ - "config": { - "name": "request methods testcase with functions", - "variables": { - "foo1": "config_bar1", - "foo2": "config_bar2", - "expect_foo1": "config_bar1", - "expect_foo2": "config_bar2" - }, - "base_url": "https://postman-echo.com", - "verify": false, - "export": [ - "foo3" - ] - }, - "teststeps": [ - { - "name": "get with params", - "variables": { - "foo1": "bar11", - "foo2": "bar21", - "sum_v": "${SumTwoInt(1, 2)}" - }, - "request": { - "method": "GET", - "url": "/get", - "params": { - "foo1": "$foo1", - "foo2": "$foo2", - "sum_v": "$sum_v" - }, - "headers": { - "User-Agent": "funplugin/${GetVersion()}" - } - }, - "extract": { - "foo3": "body.args.foo2" - }, - "validate": [ - { - "eq": [ - "status_code", - 200 - ] - }, - { - "eq": [ - "body.args.foo1", - "bar11" - ] - }, - { - "eq": [ - "body.args.sum_v", - "3" - ] - }, - { - "eq": [ - "body.args.foo2", - "bar21" - ] - } - ] - }, - { - "name": "post raw text", - "variables": { - "foo1": "bar12", - "foo3": "bar32" - }, - "request": { - "method": "POST", - "url": "/post", - "headers": { - "User-Agent": "funplugin/${GetVersion()}", - "Content-Type": "text/plain" - }, - "data": "This is expected to be sent back as part of response body: $foo1-$foo2-$foo3." - }, - "validate": [ - { - "eq": [ - "status_code", - 200 - ] - }, - { - "eq": [ - "body.data", - "This is expected to be sent back as part of response body: bar12-$expect_foo2-bar32." - ] - } - ] - }, - { - "name": "post form data", - "variables": { - "foo2": "bar23" - }, - "request": { - "method": "POST", - "url": "/post", - "headers": { - "User-Agent": "funplugin/${GetVersion()}", - "Content-Type": "application/x-www-form-urlencoded" - }, - "data": "foo1=$foo1&foo2=$foo2&foo3=$foo3" - }, - "validate": [ - { - "eq": [ - "status_code", - 200 - ] - }, - { - "eq": [ - "body.form.foo1", - "$expect_foo1" - ] - }, - { - "eq": [ - "body.form.foo2", - "bar23" - ] - }, - { - "eq": [ - "body.form.foo3", - "bar21" - ] - } - ] - } - ] -} \ No newline at end of file diff --git a/hrp/internal/scaffold/templates/testcases/demo_go_requests.yml b/hrp/internal/scaffold/templates/testcases/demo_go_requests.yml deleted file mode 100644 index 7884588f..00000000 --- a/hrp/internal/scaffold/templates/testcases/demo_go_requests.yml +++ /dev/null @@ -1,65 +0,0 @@ -config: - name: "request methods testcase with functions" - variables: - foo1: config_bar1 - foo2: config_bar2 - expect_foo1: config_bar1 - expect_foo2: config_bar2 - base_url: "https://postman-echo.com" - verify: False - export: ["foo3"] - -teststeps: -- - name: get with params - variables: - foo1: bar11 - foo2: bar21 - sum_v: "${SumTwoInt(1, 2)}" - request: - method: GET - url: /get - params: - foo1: $foo1 - foo2: $foo2 - sum_v: $sum_v - headers: - User-Agent: funplugin/${GetVersion()} - extract: - foo3: "body.args.foo2" - validate: - - eq: ["status_code", 200] - - eq: ["body.args.foo1", "bar11"] - - eq: ["body.args.sum_v", "3"] - - eq: ["body.args.foo2", "bar21"] -- - name: post raw text - variables: - foo1: "bar12" - foo3: "bar32" - request: - method: POST - url: /post - headers: - User-Agent: funplugin/${GetVersion()} - Content-Type: "text/plain" - data: "This is expected to be sent back as part of response body: $foo1-$foo2-$foo3." - validate: - - eq: ["status_code", 200] - - eq: ["body.data", "This is expected to be sent back as part of response body: bar12-$expect_foo2-bar32."] -- - name: post form data - variables: - foo2: bar23 - request: - method: POST - url: /post - headers: - User-Agent: funplugin/${GetVersion()} - Content-Type: "application/x-www-form-urlencoded" - data: "foo1=$foo1&foo2=$foo2&foo3=$foo3" - validate: - - eq: ["status_code", 200] - - eq: ["body.form.foo1", "$expect_foo1"] - - eq: ["body.form.foo2", "bar23"] - - eq: ["body.form.foo3", "bar21"] diff --git a/hrp/internal/scaffold/templates/testcases/demo_go_with_funplugin.json b/hrp/internal/scaffold/templates/testcases/demo_go_with_funplugin.json deleted file mode 100644 index 0af40519..00000000 --- a/hrp/internal/scaffold/templates/testcases/demo_go_with_funplugin.json +++ /dev/null @@ -1,176 +0,0 @@ -{ - "config": { - "name": "demo with complex mechanisms", - "base_url": "https://postman-echo.com", - "variables": { - "a": "${Sum(10, 2.3)}", - "b": 3.45, - "n": "${SumInts(1, 2, 2)}", - "varFoo1": "${gen_random_string($n)}", - "varFoo2": "${max($a, $b)}" - } - }, - "teststeps": [ - { - "name": "transaction 1 start", - "transaction": { - "name": "tran1", - "type": "start" - } - }, - { - "name": "get with params", - "request": { - "method": "GET", - "url": "/get", - "params": { - "foo1": "$varFoo1", - "foo2": "$varFoo2" - }, - "headers": { - "User-Agent": "HttpRunnerPlus" - } - }, - "variables": { - "b": 34.5, - "n": 3, - "name": "get with params", - "varFoo2": "${max($a, $b)}" - }, - "setup_hooks": [ - "${SetupHookExample($name)}" - ], - "teardown_hooks": [ - "${TeardownHookExample($name)}" - ], - "extract": { - "varFoo1": "body.args.foo1" - }, - "validate": [ - { - "check": "status_code", - "assert": "equals", - "expect": 200, - "msg": "check response status code" - }, - { - "check": "headers.\"Content-Type\"", - "assert": "startswith", - "expect": "application/json" - }, - { - "check": "body.args.foo1", - "assert": "length_equals", - "expect": 5, - "msg": "check args foo1" - }, - { - "check": "$varFoo1", - "assert": "length_equals", - "expect": 5, - "msg": "check args foo1" - }, - { - "check": "body.args.foo2", - "assert": "equals", - "expect": "34.5", - "msg": "check args foo2" - } - ] - }, - { - "name": "transaction 1 end", - "transaction": { - "name": "tran1", - "type": "end" - } - }, - { - "name": "post json data", - "request": { - "method": "POST", - "url": "/post", - "body": { - "foo1": "$varFoo1", - "foo2": "${max($a, $b)}" - } - }, - "validate": [ - { - "check": "status_code", - "assert": "equals", - "expect": 200, - "msg": "check status code" - }, - { - "check": "body.json.foo1", - "assert": "length_equals", - "expect": 5, - "msg": "check args foo1" - }, - { - "check": "body.json.foo2", - "assert": "equals", - "expect": 12.3, - "msg": "check args foo2" - } - ] - }, - { - "name": "post form data", - "request": { - "method": "POST", - "url": "/post", - "headers": { - "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8" - }, - "body": { - "foo1": "$varFoo1", - "foo2": "${max($a, $b)}", - "time": "${get_timestamp()}" - } - }, - "extract": { - "varTime": "body.form.time" - }, - "validate": [ - { - "check": "status_code", - "assert": "equals", - "expect": 200, - "msg": "check status code" - }, - { - "check": "body.form.foo1", - "assert": "length_equals", - "expect": 5, - "msg": "check args foo1" - }, - { - "check": "body.form.foo2", - "assert": "equals", - "expect": "12.3", - "msg": "check args foo2" - } - ] - }, - { - "name": "get with timestamp", - "request": { - "method": "GET", - "url": "/get", - "params": { - "time": "$varTime" - } - }, - "validate": [ - { - "check": "body.args.time", - "assert": "length_equals", - "expect": 13, - "msg": "check extracted var timestamp" - } - ] - } - ] -} \ No newline at end of file diff --git a/hrp/parser.go b/hrp/parser.go index b9693f0d..84482e3a 100644 --- a/hrp/parser.go +++ b/hrp/parser.go @@ -252,8 +252,14 @@ func (p *Parser) ParseString(raw string, variablesMapping map[string]interface{} // only support return at most one result value func (p *Parser) CallFunc(funcName string, arguments ...interface{}) (interface{}, error) { // call with plugin function - if p.plugin != nil && p.plugin.Has(funcName) { - return p.plugin.Call(funcName, arguments...) + if p.plugin != nil { + if p.plugin.Has(funcName) { + return p.plugin.Call(funcName, arguments...) + } + commonName := shared.ConvertCommonName(funcName) + if p.plugin.Has(commonName) { + return p.plugin.Call(commonName, arguments...) + } } // get builtin function diff --git a/hrp/plugin.go b/hrp/plugin.go index 72d6767d..eeeda537 100644 --- a/hrp/plugin.go +++ b/hrp/plugin.go @@ -2,7 +2,6 @@ package hrp import ( "fmt" - "github.com/httprunner/httprunner/v4/hrp/internal/build" "os" "os/signal" "path/filepath" @@ -12,6 +11,7 @@ import ( "github.com/httprunner/funplugin" "github.com/rs/zerolog/log" + "github.com/httprunner/httprunner/v4/hrp/internal/build" "github.com/httprunner/httprunner/v4/hrp/internal/sdk" ) @@ -20,6 +20,7 @@ const ( hashicorpGoPluginFile = "debugtalk.bin" // built from hashicorp go plugin hashicorpPyPluginFile = "debugtalk_gen.py" // used for hashicorp python plugin, automatically generated by HRP debugtalkPyFile = "debugtalk.py" // write by user + projectInfoFile = "proj.json" // used for ensuring root project ) func initPlugin(path string, logOn bool) (plugin funplugin.IPlugin, pluginDir string, err error) { diff --git a/hrp/runner_test.go b/hrp/runner_test.go index 4c043090..5a1327e2 100644 --- a/hrp/runner_test.go +++ b/hrp/runner_test.go @@ -12,18 +12,9 @@ import ( "github.com/httprunner/httprunner/v4/hrp/internal/scaffold" ) -func buildHashicorpGoPluginWithNoFungo() { - log.Info().Msg("[init] build hashicorp go plugin") - err := build.Run(templatesDir+"noplugin/debugtalk.go", templatesDir+"debugtalk.bin") - if err != nil { - log.Error().Err(err).Msg("build hashicorp go plugin failed") - os.Exit(1) - } -} - func buildHashicorpGoPlugin() { log.Info().Msg("[init] build hashicorp go plugin") - err := build.Run(templatesDir+"noplugin/debugtalk.go", templatesDir+"debugtalk.bin") + err := build.Run(templatesDir+"plugin/debugtalk.go", templatesDir+"debugtalk.bin") if err != nil { log.Error().Err(err).Msg("build hashicorp go plugin failed") os.Exit(1) @@ -38,7 +29,7 @@ func removeHashicorpGoPlugin() { func buildHashicorpPyPlugin() { log.Info().Msg("[init] prepare hashicorp python plugin") pluginFile := templatesDir + "debugtalk.py" - err := scaffold.CopyFile("templates/noplugin/debugtalk.py", pluginFile) + err := scaffold.CopyFile("templates/plugin/debugtalk.py", pluginFile) if err != nil { log.Error().Err(err).Msg("build hashicorp python plugin failed") os.Exit(1) @@ -55,6 +46,17 @@ func TestRunCaseWithGoPlugin(t *testing.T) { buildHashicorpGoPlugin() defer removeHashicorpGoPlugin() + assertRunTestCases(t) +} + +func TestRunCaseWithPythonPlugin(t *testing.T) { + buildHashicorpPyPlugin() + defer removeHashicorpPyPlugin() + + assertRunTestCases(t) +} + +func assertRunTestCases(t *testing.T) { testcase1 := &TestCase{ Config: NewConfig("TestCase1"). SetBaseURL("http://httpbin.org"), @@ -81,7 +83,7 @@ func TestRunCaseWithGoPlugin(t *testing.T) { }, }, ), - NewStep("testcase1-step4").CallRefCase(&demoTestCaseWithGoPluginJSONPath), + NewStep("testcase1-step4").CallRefCase(&demoTestCaseWithPluginJSONPath), }, } testcase2 := &TestCase{ @@ -96,18 +98,6 @@ func TestRunCaseWithGoPlugin(t *testing.T) { } } -func TestRunCaseWithPythonPlugin(t *testing.T) { - buildHashicorpPyPlugin() - defer removeHashicorpPyPlugin() - - r := NewRunner(t) - r.SetPluginLogOn() - err := r.Run(&demoTestCaseWithPluginJSONPath) - if err != nil { - t.Fatalf("run testcase error: %v", err) - } -} - func TestRunCaseWithThinkTime(t *testing.T) { buildHashicorpGoPlugin() defer removeHashicorpGoPlugin() @@ -170,15 +160,15 @@ func TestRunCaseWithPluginJSON(t *testing.T) { buildHashicorpGoPlugin() defer removeHashicorpGoPlugin() - err := NewRunner(nil).Run(&demoTestCaseWithGoPluginJSONPath) // hrp.Run(testCase) + err := NewRunner(nil).Run(&demoTestCaseWithPluginJSONPath) // hrp.Run(testCase) if err != nil { t.Fatal() } } func TestRunCaseWithPluginYAML(t *testing.T) { - buildHashicorpPyPlugin() - defer removeHashicorpPyPlugin() + buildHashicorpGoPlugin() + defer removeHashicorpGoPlugin() err := NewRunner(nil).Run(&demoTestCaseWithPluginYAMLPath) // hrp.Run(testCase) if err != nil { diff --git a/hrp/testcase_test.go b/hrp/testcase_test.go index 6564612e..eb6a69ae 100644 --- a/hrp/testcase_test.go +++ b/hrp/testcase_test.go @@ -15,7 +15,6 @@ const ( var ( demoTestCaseWithPluginJSONPath TestCasePath = templatesDir + "testcases/demo_with_funplugin.json" - demoTestCaseWithGoPluginJSONPath TestCasePath = templatesDir + "testcases/demo_go_with_funplugin.json" demoTestCaseWithPluginYAMLPath TestCasePath = templatesDir + "testcases/demo_with_funplugin.yaml" demoTestCaseWithoutPluginJSONPath TestCasePath = templatesDir + "testcases/demo_without_funplugin.json" demoTestCaseWithoutPluginYAMLPath TestCasePath = templatesDir + "testcases/demo_without_funplugin.yaml" From e837f63a652ee6778e804fc0a6855d85be73f045 Mon Sep 17 00:00:00 2001 From: debugtalk <mail@debugtalk.com> Date: Sat, 28 May 2022 07:54:40 +0800 Subject: [PATCH 077/109] remove user case douyin --- README.en.md | 1 - README.md | 1 - 2 files changed, 2 deletions(-) diff --git a/README.en.md b/README.en.md index df1bea1d..e9a096ee 100644 --- a/README.en.md +++ b/README.en.md @@ -108,7 +108,6 @@ Use "hrp [command] --help" for more information about a command. <a href="https://httprunner.com/docs/cases/dji-ibg"><img src="https://httprunner.com/image/logo/dji.jpeg" title="大疆 - 基于 HttpRunner 构建完整的自动化测试体系" width="60"></a> <a href="https://httprunner.com/docs/cases/bytedance-feishu"><img src="https://httprunner.com/image/logo/feishu.jpeg" title="飞书 - 使用 HttpRunner 替换已有测试平台的执行引擎" width="60"></a> -<a href="https://httprunner.com/docs/cases/bytedance-douyin"><img src="https://httprunner.com/image/logo/douyin.jpg" title="抖音 - 使用 HttpRunner 实现开放平台动态加速效果评估" width="60"></a> ## Subscribe diff --git a/README.md b/README.md index f518a140..6aa1b57a 100644 --- a/README.md +++ b/README.md @@ -101,7 +101,6 @@ Use "hrp [command] --help" for more information about a command. <a href="https://httprunner.com/docs/cases/dji-ibg"><img src="https://httprunner.com/image/logo/dji.jpeg" title="大疆 - 基于 HttpRunner 构建完整的自动化测试体系" width="60"></a> <a href="https://httprunner.com/docs/cases/bytedance-feishu"><img src="https://httprunner.com/image/logo/feishu.jpeg" title="飞书 - 使用 HttpRunner 替换已有测试平台的执行引擎" width="60"></a> -<a href="https://httprunner.com/docs/cases/bytedance-douyin"><img src="https://httprunner.com/image/logo/douyin.jpg" title="抖音 - 使用 HttpRunner 实现开放平台动态加速效果评估" width="60"></a> ## 赞助商 From 52ce77efa88209af0f09f9d57086d979cef059d4 Mon Sep 17 00:00:00 2001 From: debugtalk <mail@debugtalk.com> Date: Sat, 28 May 2022 09:40:49 +0800 Subject: [PATCH 078/109] feat: gen plugin file with hrp version --- hrp/internal/build/main.go | 11 ++++++++--- hrp/internal/build/templates/debugtalkGoTemplate | 3 ++- hrp/internal/build/templates/debugtalkPythonTemplate | 9 ++++++--- 3 files changed, 16 insertions(+), 7 deletions(-) diff --git a/hrp/internal/build/main.go b/hrp/internal/build/main.go index 2c9cd28b..72848b5b 100644 --- a/hrp/internal/build/main.go +++ b/hrp/internal/build/main.go @@ -14,9 +14,11 @@ import ( "text/template" "github.com/httprunner/funplugin/shared" - "github.com/httprunner/httprunner/v4/hrp/internal/builtin" "github.com/pkg/errors" "github.com/rs/zerolog/log" + + "github.com/httprunner/httprunner/v4/hrp/internal/builtin" + "github.com/httprunner/httprunner/v4/hrp/internal/version" ) const ( @@ -36,6 +38,7 @@ var pyTemplate string var goTemplate string type TemplateContent struct { + Version string // hrp version Fun string // funplugin package Regexps *Regexps // match import/function Imports []string // python/go import @@ -178,7 +181,8 @@ func (t *TemplateContent) genDebugTalk(path string, templ string) error { // buildGo builds debugtalk.go to debugtalk.bin func buildGo(path string, output string) error { templateContent := &TemplateContent{ - Fun: fungo, + Version: version.VERSION, + Fun: fungo, Regexps: &Regexps{ Import: regexp.MustCompile(regexGoImport), Imports: regexp.MustCompile(regexGoImports), @@ -248,7 +252,8 @@ func buildGo(path string, output string) error { // buildPy completes funppy information in debugtalk.py func buildPy(path string, output string) error { templateContent := &TemplateContent{ - Fun: funppy, + Version: version.VERSION, + Fun: funppy, Regexps: &Regexps{ FunctionName: regexp.MustCompile(regexPythonFunctionName), }, diff --git a/hrp/internal/build/templates/debugtalkGoTemplate b/hrp/internal/build/templates/debugtalkGoTemplate index d6d0a95e..842eba1d 100644 --- a/hrp/internal/build/templates/debugtalkGoTemplate +++ b/hrp/internal/build/templates/debugtalkGoTemplate @@ -9,8 +9,9 @@ import ( {{ range $function := .Functions }} {{ $function }} {{ end }} + func main() { -{{- range $idx, $functionName := .FunctionNames }} +{{- range $functionName := .FunctionNames }} fungo.Register("{{ $functionName }}", {{ $functionName }}) {{- end }} fungo.Serve() diff --git a/hrp/internal/build/templates/debugtalkPythonTemplate b/hrp/internal/build/templates/debugtalkPythonTemplate index 4da17d12..dc16657a 100644 --- a/hrp/internal/build/templates/debugtalkPythonTemplate +++ b/hrp/internal/build/templates/debugtalkPythonTemplate @@ -1,13 +1,16 @@ -{{- range $import := .Imports }} +# NOTE: Generated By hrp {{ .Version }}, DO NOT EDIT! + +{{ range $import := .Imports }} {{- $import}} {{ end }} {{ range $fromImport := .FromImports }} {{- $fromImport}} {{ end }} -{{ range $function := .Functions }} -{{ $function }} +{{ range $function := .Functions }} +{{- $function }} {{ end }} + if __name__ == "__main__": {{- range $functionName := .FunctionNames }} funppy.register("{{ $functionName }}", {{ $functionName }}) From 455a012c5d795ba39e144e079aed6b7c8480ffef Mon Sep 17 00:00:00 2001 From: debugtalk <mail@debugtalk.com> Date: Sat, 28 May 2022 09:52:22 +0800 Subject: [PATCH 079/109] change: replace template with config headers --- examples/demo-with-go-plugin/proj.json | 4 +- .../testcases/requests.json | 8 +- .../testcases/requests.yml | 6 +- examples/demo-with-py-plugin/debugtalk_gen.py | 75 +++++++++++++++++++ examples/demo-with-py-plugin/proj.json | 4 +- .../testcases/requests.json | 8 +- .../testcases/requests.yml | 6 +- examples/demo-without-plugin/proj.json | 4 +- examples/empty-demo-without-plugin/.env | 3 + examples/empty-demo-without-plugin/.gitignore | 14 ++++ examples/empty-demo-without-plugin/har/.keep | 0 examples/empty-demo-without-plugin/proj.json | 6 ++ .../testcases/requests.json | 25 +++++++ .../templates/testcases/demo_requests.json | 8 +- .../templates/testcases/demo_requests.yml | 6 +- 15 files changed, 144 insertions(+), 33 deletions(-) create mode 100644 examples/demo-with-py-plugin/debugtalk_gen.py create mode 100644 examples/empty-demo-without-plugin/.env create mode 100644 examples/empty-demo-without-plugin/.gitignore create mode 100644 examples/empty-demo-without-plugin/har/.keep create mode 100644 examples/empty-demo-without-plugin/proj.json create mode 100644 examples/empty-demo-without-plugin/testcases/requests.json diff --git a/examples/demo-with-go-plugin/proj.json b/examples/demo-with-go-plugin/proj.json index 6e7a483a..97b52ffa 100644 --- a/examples/demo-with-go-plugin/proj.json +++ b/examples/demo-with-go-plugin/proj.json @@ -1,6 +1,6 @@ { "project_name": "demo-with-go-plugin", - "project_path": "/Users/xxxxx/go/src/github.com/httprunner/httprunner/examples/demo-with-go-plugin", - "create_time": "2022-05-28T02:00:18.084185+08:00", + "project_path": "/Users/debugtalk/MyProjects/HttpRunner-dev/httprunner/examples/demo-with-go-plugin", + "create_time": "2022-05-28T09:46:47.418012+08:00", "hrp_version": "v4.1.0-beta" } \ No newline at end of file diff --git a/examples/demo-with-go-plugin/testcases/requests.json b/examples/demo-with-go-plugin/testcases/requests.json index 162632b4..a05d5277 100644 --- a/examples/demo-with-go-plugin/testcases/requests.json +++ b/examples/demo-with-go-plugin/testcases/requests.json @@ -7,6 +7,9 @@ "expect_foo1": "config_bar1", "expect_foo2": "config_bar2" }, + "headers": { + "User-Agent": "funplugin/${get_version()}" + }, "base_url": "https://postman-echo.com", "verify": false, "export": [ @@ -28,9 +31,6 @@ "foo1": "$foo1", "foo2": "$foo2", "sum_v": "$sum_v" - }, - "headers": { - "User-Agent": "funplugin/${get_version()}" } }, "extract": { @@ -73,7 +73,6 @@ "method": "POST", "url": "/post", "headers": { - "User-Agent": "funplugin/${get_version()}", "Content-Type": "text/plain" }, "body": "This is expected to be sent back as part of response body: $foo1-$foo2-$foo3." @@ -102,7 +101,6 @@ "method": "POST", "url": "/post", "headers": { - "User-Agent": "funplugin/${get_version()}", "Content-Type": "application/x-www-form-urlencoded" }, "body": "foo1=$foo1&foo2=$foo2&foo3=$foo3" diff --git a/examples/demo-with-go-plugin/testcases/requests.yml b/examples/demo-with-go-plugin/testcases/requests.yml index 034dbefb..7fcfbe1e 100644 --- a/examples/demo-with-go-plugin/testcases/requests.yml +++ b/examples/demo-with-go-plugin/testcases/requests.yml @@ -5,6 +5,8 @@ config: foo2: config_bar2 expect_foo1: config_bar1 expect_foo2: config_bar2 + headers: + User-Agent: funplugin/${get_version()} verify: False export: ["foo3"] @@ -22,8 +24,6 @@ teststeps: foo1: $foo1 foo2: $foo2 sum_v: $sum_v - headers: - User-Agent: funplugin/${get_version()} extract: foo3: "body.args.foo2" validate: @@ -40,7 +40,6 @@ teststeps: method: POST url: $base_url/post headers: - User-Agent: funplugin/${get_version()} Content-Type: "text/plain" body: "This is expected to be sent back as part of response body: $foo1-$foo2-$foo3." validate: @@ -54,7 +53,6 @@ teststeps: method: POST url: $base_url/post headers: - User-Agent: funplugin/${get_version()} Content-Type: "application/x-www-form-urlencoded" body: "foo1=$foo1&foo2=$foo2&foo3=$foo3" validate: diff --git a/examples/demo-with-py-plugin/debugtalk_gen.py b/examples/demo-with-py-plugin/debugtalk_gen.py new file mode 100644 index 00000000..76adcbdd --- /dev/null +++ b/examples/demo-with-py-plugin/debugtalk_gen.py @@ -0,0 +1,75 @@ +# NOTE: Generated By hrp v4.1.0-beta, DO NOT EDIT! + +import logging +import time +import funppy + +from typing import List + + +def get_version(): + return funppy.__version__ + + +def sleep(n_secs): + time.sleep(n_secs) + + +def sum(*args): + result = 0 + for arg in args: + result += arg + return result + + +def sum_ints(*args: List[int]) -> int: + result = 0 + for arg in args: + result += arg + return result + + +def sum_two_int(a: int, b: int) -> int: + return a + b + + +def sum_two_string(a: str, b: str) -> str: + return a + b + + +def sum_strings(*args: List[str]) -> str: + result = "" + for arg in args: + result += arg + return result + + +def concatenate(*args: List[str]) -> str: + result = "" + for arg in args: + result += str(arg) + return result + + +def setup_hook_example(name): + logging.warning("setup_hook_example") + return f"setup_hook_example: {name}" + + +def teardown_hook_example(name): + logging.warning("teardown_hook_example") + return f"teardown_hook_example: {name}" + + +if __name__ == "__main__": + funppy.register("get_version", get_version) + funppy.register("sleep", sleep) + funppy.register("sum", sum) + funppy.register("sum_ints", sum_ints) + funppy.register("sum_two_int", sum_two_int) + funppy.register("sum_two_string", sum_two_string) + funppy.register("sum_strings", sum_strings) + funppy.register("concatenate", concatenate) + funppy.register("setup_hook_example", setup_hook_example) + funppy.register("teardown_hook_example", teardown_hook_example) + funppy.serve() diff --git a/examples/demo-with-py-plugin/proj.json b/examples/demo-with-py-plugin/proj.json index 9d8cce0e..a00b9247 100644 --- a/examples/demo-with-py-plugin/proj.json +++ b/examples/demo-with-py-plugin/proj.json @@ -1,6 +1,6 @@ { "project_name": "demo-with-py-plugin", - "project_path": "/Users/xxxxx/go/src/github.com/httprunner/httprunner/examples/demo-with-py-plugin", - "create_time": "2022-05-28T02:00:28.517914+08:00", + "project_path": "/Users/debugtalk/MyProjects/HttpRunner-dev/httprunner/examples/demo-with-py-plugin", + "create_time": "2022-05-28T09:46:52.419292+08:00", "hrp_version": "v4.1.0-beta" } \ No newline at end of file diff --git a/examples/demo-with-py-plugin/testcases/requests.json b/examples/demo-with-py-plugin/testcases/requests.json index 162632b4..a05d5277 100644 --- a/examples/demo-with-py-plugin/testcases/requests.json +++ b/examples/demo-with-py-plugin/testcases/requests.json @@ -7,6 +7,9 @@ "expect_foo1": "config_bar1", "expect_foo2": "config_bar2" }, + "headers": { + "User-Agent": "funplugin/${get_version()}" + }, "base_url": "https://postman-echo.com", "verify": false, "export": [ @@ -28,9 +31,6 @@ "foo1": "$foo1", "foo2": "$foo2", "sum_v": "$sum_v" - }, - "headers": { - "User-Agent": "funplugin/${get_version()}" } }, "extract": { @@ -73,7 +73,6 @@ "method": "POST", "url": "/post", "headers": { - "User-Agent": "funplugin/${get_version()}", "Content-Type": "text/plain" }, "body": "This is expected to be sent back as part of response body: $foo1-$foo2-$foo3." @@ -102,7 +101,6 @@ "method": "POST", "url": "/post", "headers": { - "User-Agent": "funplugin/${get_version()}", "Content-Type": "application/x-www-form-urlencoded" }, "body": "foo1=$foo1&foo2=$foo2&foo3=$foo3" diff --git a/examples/demo-with-py-plugin/testcases/requests.yml b/examples/demo-with-py-plugin/testcases/requests.yml index 034dbefb..7fcfbe1e 100644 --- a/examples/demo-with-py-plugin/testcases/requests.yml +++ b/examples/demo-with-py-plugin/testcases/requests.yml @@ -5,6 +5,8 @@ config: foo2: config_bar2 expect_foo1: config_bar1 expect_foo2: config_bar2 + headers: + User-Agent: funplugin/${get_version()} verify: False export: ["foo3"] @@ -22,8 +24,6 @@ teststeps: foo1: $foo1 foo2: $foo2 sum_v: $sum_v - headers: - User-Agent: funplugin/${get_version()} extract: foo3: "body.args.foo2" validate: @@ -40,7 +40,6 @@ teststeps: method: POST url: $base_url/post headers: - User-Agent: funplugin/${get_version()} Content-Type: "text/plain" body: "This is expected to be sent back as part of response body: $foo1-$foo2-$foo3." validate: @@ -54,7 +53,6 @@ teststeps: method: POST url: $base_url/post headers: - User-Agent: funplugin/${get_version()} Content-Type: "application/x-www-form-urlencoded" body: "foo1=$foo1&foo2=$foo2&foo3=$foo3" validate: diff --git a/examples/demo-without-plugin/proj.json b/examples/demo-without-plugin/proj.json index e622b2a7..489c9964 100644 --- a/examples/demo-without-plugin/proj.json +++ b/examples/demo-without-plugin/proj.json @@ -1,6 +1,6 @@ { "project_name": "demo-without-plugin", - "project_path": "/Users/xxxxx/go/src/github.com/httprunner/httprunner/examples/demo-without-plugin", - "create_time": "2022-05-28T02:00:29.191678+08:00", + "project_path": "/Users/debugtalk/MyProjects/HttpRunner-dev/httprunner/examples/demo-without-plugin", + "create_time": "2022-05-28T09:46:53.671932+08:00", "hrp_version": "v4.1.0-beta" } \ No newline at end of file diff --git a/examples/empty-demo-without-plugin/.env b/examples/empty-demo-without-plugin/.env new file mode 100644 index 00000000..59ecc742 --- /dev/null +++ b/examples/empty-demo-without-plugin/.env @@ -0,0 +1,3 @@ +base_url=https://postman-echo.com +USERNAME=debugtalk +PASSWORD=123456 \ No newline at end of file diff --git a/examples/empty-demo-without-plugin/.gitignore b/examples/empty-demo-without-plugin/.gitignore new file mode 100644 index 00000000..4c8cb60c --- /dev/null +++ b/examples/empty-demo-without-plugin/.gitignore @@ -0,0 +1,14 @@ +reports/ +*.so +.vscode/ +.idea/ +.DS_Store +output/ +__pycache__/ +*.pyc +.python-version +logs/ + +# plugin +debugtalk.bin +debugtalk.so diff --git a/examples/empty-demo-without-plugin/har/.keep b/examples/empty-demo-without-plugin/har/.keep new file mode 100644 index 00000000..e69de29b diff --git a/examples/empty-demo-without-plugin/proj.json b/examples/empty-demo-without-plugin/proj.json new file mode 100644 index 00000000..cd42ca8d --- /dev/null +++ b/examples/empty-demo-without-plugin/proj.json @@ -0,0 +1,6 @@ +{ + "project_name": "empty-demo-without-plugin", + "project_path": "/Users/debugtalk/MyProjects/HttpRunner-dev/httprunner/examples/empty-demo-without-plugin", + "create_time": "2022-05-28T09:46:53.794409+08:00", + "hrp_version": "v4.1.0-beta" +} \ No newline at end of file diff --git a/examples/empty-demo-without-plugin/testcases/requests.json b/examples/empty-demo-without-plugin/testcases/requests.json new file mode 100644 index 00000000..fc76e4aa --- /dev/null +++ b/examples/empty-demo-without-plugin/testcases/requests.json @@ -0,0 +1,25 @@ +{ + "config": { + "name": "request methods testcase: empty testcase", + "variables": null, + "verify": false + }, + "teststeps": [ + { + "name": "", + "variables": null, + "request": { + "method": "GET", + "url": "https://" + }, + "validate": [ + { + "check": "status_code", + "assert": "equal", + "expect": 200, + "msg": "check status_code" + } + ] + } + ] +} \ No newline at end of file diff --git a/hrp/internal/scaffold/templates/testcases/demo_requests.json b/hrp/internal/scaffold/templates/testcases/demo_requests.json index 162632b4..a05d5277 100644 --- a/hrp/internal/scaffold/templates/testcases/demo_requests.json +++ b/hrp/internal/scaffold/templates/testcases/demo_requests.json @@ -7,6 +7,9 @@ "expect_foo1": "config_bar1", "expect_foo2": "config_bar2" }, + "headers": { + "User-Agent": "funplugin/${get_version()}" + }, "base_url": "https://postman-echo.com", "verify": false, "export": [ @@ -28,9 +31,6 @@ "foo1": "$foo1", "foo2": "$foo2", "sum_v": "$sum_v" - }, - "headers": { - "User-Agent": "funplugin/${get_version()}" } }, "extract": { @@ -73,7 +73,6 @@ "method": "POST", "url": "/post", "headers": { - "User-Agent": "funplugin/${get_version()}", "Content-Type": "text/plain" }, "body": "This is expected to be sent back as part of response body: $foo1-$foo2-$foo3." @@ -102,7 +101,6 @@ "method": "POST", "url": "/post", "headers": { - "User-Agent": "funplugin/${get_version()}", "Content-Type": "application/x-www-form-urlencoded" }, "body": "foo1=$foo1&foo2=$foo2&foo3=$foo3" diff --git a/hrp/internal/scaffold/templates/testcases/demo_requests.yml b/hrp/internal/scaffold/templates/testcases/demo_requests.yml index 034dbefb..7fcfbe1e 100644 --- a/hrp/internal/scaffold/templates/testcases/demo_requests.yml +++ b/hrp/internal/scaffold/templates/testcases/demo_requests.yml @@ -5,6 +5,8 @@ config: foo2: config_bar2 expect_foo1: config_bar1 expect_foo2: config_bar2 + headers: + User-Agent: funplugin/${get_version()} verify: False export: ["foo3"] @@ -22,8 +24,6 @@ teststeps: foo1: $foo1 foo2: $foo2 sum_v: $sum_v - headers: - User-Agent: funplugin/${get_version()} extract: foo3: "body.args.foo2" validate: @@ -40,7 +40,6 @@ teststeps: method: POST url: $base_url/post headers: - User-Agent: funplugin/${get_version()} Content-Type: "text/plain" body: "This is expected to be sent back as part of response body: $foo1-$foo2-$foo3." validate: @@ -54,7 +53,6 @@ teststeps: method: POST url: $base_url/post headers: - User-Agent: funplugin/${get_version()} Content-Type: "application/x-www-form-urlencoded" body: "foo1=$foo1&foo2=$foo2&foo3=$foo3" validate: From 1977a7781fb6bc435d94b4fbb1d0c78aa4a01523 Mon Sep 17 00:00:00 2001 From: debugtalk <mail@debugtalk.com> Date: Sat, 28 May 2022 12:08:15 +0800 Subject: [PATCH 080/109] change: rename demo empty project --- .../{empty-demo-without-plugin => demo-empty-project}/.env | 0 .../.gitignore | 0 .../har/.keep | 0 examples/demo-empty-project/proj.json | 6 ++++++ .../testcases/requests.json | 0 examples/demo-with-go-plugin/proj.json | 2 +- examples/demo-with-py-plugin/proj.json | 2 +- examples/demo-without-plugin/proj.json | 2 +- examples/empty-demo-without-plugin/proj.json | 6 ------ hrp/internal/scaffold/examples_test.go | 2 +- 10 files changed, 10 insertions(+), 10 deletions(-) rename examples/{empty-demo-without-plugin => demo-empty-project}/.env (100%) rename examples/{empty-demo-without-plugin => demo-empty-project}/.gitignore (100%) rename examples/{empty-demo-without-plugin => demo-empty-project}/har/.keep (100%) create mode 100644 examples/demo-empty-project/proj.json rename examples/{empty-demo-without-plugin => demo-empty-project}/testcases/requests.json (100%) delete mode 100644 examples/empty-demo-without-plugin/proj.json diff --git a/examples/empty-demo-without-plugin/.env b/examples/demo-empty-project/.env similarity index 100% rename from examples/empty-demo-without-plugin/.env rename to examples/demo-empty-project/.env diff --git a/examples/empty-demo-without-plugin/.gitignore b/examples/demo-empty-project/.gitignore similarity index 100% rename from examples/empty-demo-without-plugin/.gitignore rename to examples/demo-empty-project/.gitignore diff --git a/examples/empty-demo-without-plugin/har/.keep b/examples/demo-empty-project/har/.keep similarity index 100% rename from examples/empty-demo-without-plugin/har/.keep rename to examples/demo-empty-project/har/.keep diff --git a/examples/demo-empty-project/proj.json b/examples/demo-empty-project/proj.json new file mode 100644 index 00000000..00831ae5 --- /dev/null +++ b/examples/demo-empty-project/proj.json @@ -0,0 +1,6 @@ +{ + "project_name": "demo-empty-project", + "project_path": "/Users/debugtalk/MyProjects/HttpRunner-dev/httprunner/examples/demo-empty-project", + "create_time": "2022-05-28T12:05:34.37753+08:00", + "hrp_version": "v4.1.0-beta" +} \ No newline at end of file diff --git a/examples/empty-demo-without-plugin/testcases/requests.json b/examples/demo-empty-project/testcases/requests.json similarity index 100% rename from examples/empty-demo-without-plugin/testcases/requests.json rename to examples/demo-empty-project/testcases/requests.json diff --git a/examples/demo-with-go-plugin/proj.json b/examples/demo-with-go-plugin/proj.json index 97b52ffa..c73eaf1a 100644 --- a/examples/demo-with-go-plugin/proj.json +++ b/examples/demo-with-go-plugin/proj.json @@ -1,6 +1,6 @@ { "project_name": "demo-with-go-plugin", "project_path": "/Users/debugtalk/MyProjects/HttpRunner-dev/httprunner/examples/demo-with-go-plugin", - "create_time": "2022-05-28T09:46:47.418012+08:00", + "create_time": "2022-05-28T12:05:24.919126+08:00", "hrp_version": "v4.1.0-beta" } \ No newline at end of file diff --git a/examples/demo-with-py-plugin/proj.json b/examples/demo-with-py-plugin/proj.json index a00b9247..62efacab 100644 --- a/examples/demo-with-py-plugin/proj.json +++ b/examples/demo-with-py-plugin/proj.json @@ -1,6 +1,6 @@ { "project_name": "demo-with-py-plugin", "project_path": "/Users/debugtalk/MyProjects/HttpRunner-dev/httprunner/examples/demo-with-py-plugin", - "create_time": "2022-05-28T09:46:52.419292+08:00", + "create_time": "2022-05-28T12:05:33.048579+08:00", "hrp_version": "v4.1.0-beta" } \ No newline at end of file diff --git a/examples/demo-without-plugin/proj.json b/examples/demo-without-plugin/proj.json index 489c9964..9f1070cc 100644 --- a/examples/demo-without-plugin/proj.json +++ b/examples/demo-without-plugin/proj.json @@ -1,6 +1,6 @@ { "project_name": "demo-without-plugin", "project_path": "/Users/debugtalk/MyProjects/HttpRunner-dev/httprunner/examples/demo-without-plugin", - "create_time": "2022-05-28T09:46:53.671932+08:00", + "create_time": "2022-05-28T12:05:34.258157+08:00", "hrp_version": "v4.1.0-beta" } \ No newline at end of file diff --git a/examples/empty-demo-without-plugin/proj.json b/examples/empty-demo-without-plugin/proj.json deleted file mode 100644 index cd42ca8d..00000000 --- a/examples/empty-demo-without-plugin/proj.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "project_name": "empty-demo-without-plugin", - "project_path": "/Users/debugtalk/MyProjects/HttpRunner-dev/httprunner/examples/empty-demo-without-plugin", - "create_time": "2022-05-28T09:46:53.794409+08:00", - "hrp_version": "v4.1.0-beta" -} \ No newline at end of file diff --git a/hrp/internal/scaffold/examples_test.go b/hrp/internal/scaffold/examples_test.go index 3bde77b0..d480ffb6 100644 --- a/hrp/internal/scaffold/examples_test.go +++ b/hrp/internal/scaffold/examples_test.go @@ -23,7 +23,7 @@ func TestGenDemoExamples(t *testing.T) { t.Fatal() } - dir = "../../../examples/empty-demo-without-plugin" + dir = "../../../examples/demo-empty-project" err = CreateScaffold(dir, Empty, true) if err != nil { t.Fatal() From 2fc23545486170db72948ac67207ab0dac70ecbe Mon Sep 17 00:00:00 2001 From: debugtalk <mail@debugtalk.com> Date: Sat, 28 May 2022 12:08:36 +0800 Subject: [PATCH 081/109] bump version to v4.1.0 --- hrp/internal/version/VERSION | 2 +- httprunner/__init__.py | 2 +- pyproject.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/hrp/internal/version/VERSION b/hrp/internal/version/VERSION index 9453e976..5469c48c 100644 --- a/hrp/internal/version/VERSION +++ b/hrp/internal/version/VERSION @@ -1 +1 @@ -v4.1.0-beta \ No newline at end of file +v4.1.0 \ No newline at end of file diff --git a/httprunner/__init__.py b/httprunner/__init__.py index 704aa8d5..562a9028 100644 --- a/httprunner/__init__.py +++ b/httprunner/__init__.py @@ -1,4 +1,4 @@ -__version__ = "v4.1.0-beta" +__version__ = "v4.1.0" __description__ = "One-stop solution for HTTP(S) testing." diff --git a/pyproject.toml b/pyproject.toml index 0be11d44..9f56803d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "httprunner" -version = "v4.1.0-beta" +version = "v4.1.0" description = "One-stop solution for HTTP(S) testing." license = "Apache-2.0" readme = "README.md" From 43c800783ac06a0edb674bdaecad4bb3863ae08a Mon Sep 17 00:00:00 2001 From: debugtalk <mail@debugtalk.com> Date: Sat, 28 May 2022 12:37:17 +0800 Subject: [PATCH 082/109] doc: update changelog --- docs/CHANGELOG.md | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index a50a37a9..988769fb 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -1,16 +1,20 @@ # Release History -## v4.1.0 (2022-05-25) +## v4.1.0 (2022-05-28) - feat: add `wiki` sub-command to open httprunner website +- feat: add `build` sub-command for function plugin **go version** +- feat #1268: convert postman collection to HttpRunner testcase +- feat #1291: run testcases in v2/v3 JSON/YAML format with hrp run/boom command +- feat #1280: support creating empty scaffold project - fix #1308: load `.env` file as environment variables - fix #1309: locate plugin file upward recursively until system root dir -- refactor: move base_url to config env -- feat: support converting Postman collection to HttpRunner testcase -- refactor: improve the extensibility of `hrp convert` using interface `ICaseConverter` +- fix #1315: failed to generate a report in failfast mode +- refactor: move base_url to config `environs` +- refactor: implement testcase conversions with `hrp convert` ## v4.1.0-beta (2022-05-21) From ebc0a3768cfe7b653c0c508741ed20cea4374d3a Mon Sep 17 00:00:00 2001 From: debugtalk <mail@debugtalk.com> Date: Sat, 28 May 2022 15:51:21 +0800 Subject: [PATCH 083/109] refactor: hrp build --- .github/workflows/hrp-scaffold.yml | 6 +- examples/demo-empty-project/proj.json | 4 +- .../demo-with-go-plugin/plugin/debugtalk.go | 17 +- examples/demo-with-go-plugin/plugin/go.mod | 5 - examples/demo-with-go-plugin/plugin/go.sum | 196 ------------------ examples/demo-with-go-plugin/proj.json | 4 +- .../testcases/ref_testcase.yml | 2 +- .../testcases/requests.json | 2 +- .../testcases/requests.yml | 2 +- examples/demo-with-py-plugin/debugtalk.py | 20 +- examples/demo-with-py-plugin/proj.json | 4 +- .../testcases/ref_testcase.yml | 2 +- .../testcases/requests.json | 2 +- .../testcases/requests.yml | 2 +- examples/demo-without-plugin/proj.json | 4 +- hrp/internal/build/main.go | 55 ++--- hrp/internal/build/main_test.go | 11 +- hrp/internal/build/plugin/debugtalk.go | 44 ---- .../build/templates/debugtalkGoTemplate | 9 +- hrp/internal/scaffold/main.go | 20 +- .../templates}/debugtalk.py | 4 +- .../scaffold/templates/plugin/debugtalk.go | 17 +- .../scaffold/templates/plugin/debugtalk.py | 20 +- .../templates/plugin/debugtalk_gen.go | 16 ++ .../templates/plugin}/debugtalk_gen.py | 8 +- .../templates/testcases/demo_ref_testcase.yml | 2 +- .../testcases/demo_ref_testcase_test.py | 2 +- .../templates/testcases/demo_requests.json | 2 +- .../templates/testcases/demo_requests.yml | 2 +- .../templates/testcases/demo_requests_test.py | 6 +- 30 files changed, 92 insertions(+), 398 deletions(-) delete mode 100644 examples/demo-with-go-plugin/plugin/go.mod delete mode 100644 examples/demo-with-go-plugin/plugin/go.sum delete mode 100644 hrp/internal/build/plugin/debugtalk.go rename hrp/internal/{build/plugin => scaffold/templates}/debugtalk.py (95%) create mode 100644 hrp/internal/scaffold/templates/plugin/debugtalk_gen.go rename {examples/demo-with-py-plugin => hrp/internal/scaffold/templates/plugin}/debugtalk_gen.py (90%) diff --git a/.github/workflows/hrp-scaffold.yml b/.github/workflows/hrp-scaffold.yml index 08d2ee46..e513435a 100644 --- a/.github/workflows/hrp-scaffold.yml +++ b/.github/workflows/hrp-scaffold.yml @@ -58,15 +58,17 @@ jobs: run: make build - name: Run start project run: ./output/hrp startproject demo --go + - name: Build plugin + run: ./output/hrp build -o demo/debugtalk.bin demo/plugin/debugtalk.go - name: Run generated demo tests run: ./output/hrp run demo/testcases/ - name: Run API test demo in examples run: | - go build -o examples/demo-with-go-plugin/debugtalk.bin examples/demo-with-go-plugin/plugin/debugtalk.go + ./output/hrp build -o examples/demo-with-go-plugin/debugtalk.bin examples/demo-with-go-plugin/plugin/debugtalk.go ./output/hrp run examples/demo-with-go-plugin/testcases/demo.json - name: Run load test demo in examples run: | - go build -o examples/demo-with-go-plugin/debugtalk.bin examples/demo-with-go-plugin/plugin/debugtalk.go + ./output/hrp build -o examples/demo-with-go-plugin/debugtalk.bin examples/demo-with-go-plugin/plugin/debugtalk.go ./output/hrp boom examples/demo-with-go-plugin/testcases/demo.json --spawn-count 10 --spawn-rate 10 --loop-count 10 scaffold-without-custom-plugin: diff --git a/examples/demo-empty-project/proj.json b/examples/demo-empty-project/proj.json index 00831ae5..e35e90c0 100644 --- a/examples/demo-empty-project/proj.json +++ b/examples/demo-empty-project/proj.json @@ -1,6 +1,6 @@ { "project_name": "demo-empty-project", "project_path": "/Users/debugtalk/MyProjects/HttpRunner-dev/httprunner/examples/demo-empty-project", - "create_time": "2022-05-28T12:05:34.37753+08:00", - "hrp_version": "v4.1.0-beta" + "create_time": "2022-05-28T21:41:06.430799+08:00", + "hrp_version": "v4.1.0" } \ No newline at end of file diff --git a/examples/demo-with-go-plugin/plugin/debugtalk.go b/examples/demo-with-go-plugin/plugin/debugtalk.go index b3b39400..73deb244 100644 --- a/examples/demo-with-go-plugin/plugin/debugtalk.go +++ b/examples/demo-with-go-plugin/plugin/debugtalk.go @@ -2,8 +2,6 @@ package main import ( "fmt" - - "github.com/httprunner/funplugin/fungo" ) func SumTwoInt(a, b int) int { @@ -41,17 +39,6 @@ func TeardownHookExample(args string) string { return fmt.Sprintf("step name: %v, teardown...", args) } -func GetVersion() string { - return fungo.Version -} - -func main() { - fungo.Register("get_version", GetVersion) - fungo.Register("sum_ints", SumInts) - fungo.Register("sum_two_int", SumTwoInt) - fungo.Register("sum_two", SumTwoInt) - fungo.Register("sum", Sum) - fungo.Register("setup_hook_example", SetupHookExample) - fungo.Register("teardown_hook_example", TeardownHookExample) - fungo.Serve() +func GetUserAgent() string { + return "hrp/fungo" } diff --git a/examples/demo-with-go-plugin/plugin/go.mod b/examples/demo-with-go-plugin/plugin/go.mod deleted file mode 100644 index a36c1c27..00000000 --- a/examples/demo-with-go-plugin/plugin/go.mod +++ /dev/null @@ -1,5 +0,0 @@ -module plugin - -go 1.16 - -require github.com/httprunner/funplugin v0.4.7 // indirect diff --git a/examples/demo-with-go-plugin/plugin/go.sum b/examples/demo-with-go-plugin/plugin/go.sum deleted file mode 100644 index bae78b47..00000000 --- a/examples/demo-with-go-plugin/plugin/go.sum +++ /dev/null @@ -1,196 +0,0 @@ -cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= -github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= -github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= -github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= -github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= -github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= -github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= -github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= -github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= -github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= -github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= -github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= -github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= -github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= -github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= -github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= -github.com/hashicorp/go-hclog v0.14.1/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= -github.com/hashicorp/go-hclog v1.1.0 h1:QsGcniKx5/LuX2eYoeL+Np3UKYPNaN7YKpTh29h8rbw= -github.com/hashicorp/go-hclog v1.1.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= -github.com/hashicorp/go-plugin v1.4.3 h1:DXmvivbWD5qdiBts9TpBC7BYL1Aia5sxbRgQB+v6UZM= -github.com/hashicorp/go-plugin v1.4.3/go.mod h1:5fGEH17QVwTTcR0zV7yhDPLLmFX9YSZ38b18Udy6vYQ= -github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb h1:b5rjCoWHc7eqmAS4/qyk21ZsHyb6Mxv/jykxvNTkU4M= -github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= -github.com/httprunner/funplugin v0.4.7 h1:bmk84BL8oPGE/rgxCuHgPcwJtBnwDzm/ocmFY/cKcos= -github.com/httprunner/funplugin v0.4.7/go.mod h1:vPyeJIfbpGe0epZZtAV0wCn16gLY9+imSw/zfxq0Lcc= -github.com/jhump/protoreflect v1.6.0/go.mod h1:eaTn3RZAmMBcV0fifFvlm6VHNz3wSkYyXYWUh7ymB74= -github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= -github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= -github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA= -github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= -github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= -github.com/mattn/go-isatty v0.0.10 h1:qxFzApOv4WsAL965uUPIsXzAKCZxN2p9UqdhFS4ZW10= -github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= -github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77 h1:7GoSOOW2jpsfkntVKaS2rAr1TJqfcxotyaUcuxoZSzg= -github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= -github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc= -github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= -github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= -github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw= -github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= -github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= -github.com/rs/xid v1.3.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= -github.com/rs/zerolog v1.26.1 h1:/ihwxqH+4z8UxyI70wM1z9yCvkWcfz/a3mj48k/Zngc= -github.com/rs/zerolog v1.26.1/go.mod h1:/wSSJWX7lVrsOwlbyTRSOJvqRlc+WjWlfes+CiJ+tmc= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= -go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20211215165025-cf75a172585e/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= -golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= -golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/net v0.0.0-20180530234432-1e491301e022/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= -golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20220225172249-27dd8689420f h1:oA4XRj0qtSt8Yo1Zms0CUlsT3KG69V2UGQWPBxujDmc= -golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5 h1:y/woIyUBFbpQGKS0u1aHF/40WUDnek3fPOyD08H5Vng= -golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= -golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/genproto v0.0.0-20170818010345-ee236bd376b0/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20220314164441-57ef72a4c106 h1:ErU+UA6wxadoU8nWrsy5MZUVBs75K17zUCsUCIfrXCE= -google.golang.org/genproto v0.0.0-20220314164441-57ef72a4c106/go.mod h1:hAL49I2IFola2sVEjAn7MEwsja0xp51I0tlGAf9hz4E= -google.golang.org/grpc v1.8.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= -google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= -google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= -google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.45.0 h1:NEpgUqV3Z+ZjkqMsxMg11IaDrXY4RY6CQukSGK0uI1M= -google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ= -google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= -google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= -google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= -google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= -google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= -google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= -google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ= -google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/examples/demo-with-go-plugin/proj.json b/examples/demo-with-go-plugin/proj.json index c73eaf1a..9b1626b1 100644 --- a/examples/demo-with-go-plugin/proj.json +++ b/examples/demo-with-go-plugin/proj.json @@ -1,6 +1,6 @@ { "project_name": "demo-with-go-plugin", "project_path": "/Users/debugtalk/MyProjects/HttpRunner-dev/httprunner/examples/demo-with-go-plugin", - "create_time": "2022-05-28T12:05:24.919126+08:00", - "hrp_version": "v4.1.0-beta" + "create_time": "2022-05-28T21:41:04.688435+08:00", + "hrp_version": "v4.1.0" } \ No newline at end of file diff --git a/examples/demo-with-go-plugin/testcases/ref_testcase.yml b/examples/demo-with-go-plugin/testcases/ref_testcase.yml index 0816481c..c0932124 100644 --- a/examples/demo-with-go-plugin/testcases/ref_testcase.yml +++ b/examples/demo-with-go-plugin/testcases/ref_testcase.yml @@ -24,7 +24,7 @@ teststeps: method: POST url: /post headers: - User-Agent: funplugin/${get_version()} + User-Agent: ${get_user_agent()} Content-Type: "application/x-www-form-urlencoded" body: "foo1=$foo1&foo2=$foo3" validate: diff --git a/examples/demo-with-go-plugin/testcases/requests.json b/examples/demo-with-go-plugin/testcases/requests.json index a05d5277..75c464f9 100644 --- a/examples/demo-with-go-plugin/testcases/requests.json +++ b/examples/demo-with-go-plugin/testcases/requests.json @@ -8,7 +8,7 @@ "expect_foo2": "config_bar2" }, "headers": { - "User-Agent": "funplugin/${get_version()}" + "User-Agent": "${get_user_agent()}" }, "base_url": "https://postman-echo.com", "verify": false, diff --git a/examples/demo-with-go-plugin/testcases/requests.yml b/examples/demo-with-go-plugin/testcases/requests.yml index 7fcfbe1e..1db4e4d1 100644 --- a/examples/demo-with-go-plugin/testcases/requests.yml +++ b/examples/demo-with-go-plugin/testcases/requests.yml @@ -6,7 +6,7 @@ config: expect_foo1: config_bar1 expect_foo2: config_bar2 headers: - User-Agent: funplugin/${get_version()} + User-Agent: ${get_user_agent()} verify: False export: ["foo3"] diff --git a/examples/demo-with-py-plugin/debugtalk.py b/examples/demo-with-py-plugin/debugtalk.py index 9fd41120..ea48ff48 100644 --- a/examples/demo-with-py-plugin/debugtalk.py +++ b/examples/demo-with-py-plugin/debugtalk.py @@ -2,11 +2,9 @@ import logging import time from typing import List -import funppy - -def get_version(): - return funppy.__version__ +def get_user_agent(): + return "hrp/funppy" def sleep(n_secs): @@ -57,17 +55,3 @@ def setup_hook_example(name): def teardown_hook_example(name): logging.warning("teardown_hook_example") return f"teardown_hook_example: {name}" - - -if __name__ == "__main__": - funppy.register("get_version", get_version) - funppy.register("sum", sum) - funppy.register("sum_ints", sum_ints) - funppy.register("concatenate", concatenate) - funppy.register("sum_two_int", sum_two_int) - funppy.register("sum_two", sum_two_int) - funppy.register("sum_two_string", sum_two_string) - funppy.register("sum_strings", sum_strings) - funppy.register("setup_hook_example", setup_hook_example) - funppy.register("teardown_hook_example", teardown_hook_example) - funppy.serve() diff --git a/examples/demo-with-py-plugin/proj.json b/examples/demo-with-py-plugin/proj.json index 62efacab..ff4fa255 100644 --- a/examples/demo-with-py-plugin/proj.json +++ b/examples/demo-with-py-plugin/proj.json @@ -1,6 +1,6 @@ { "project_name": "demo-with-py-plugin", "project_path": "/Users/debugtalk/MyProjects/HttpRunner-dev/httprunner/examples/demo-with-py-plugin", - "create_time": "2022-05-28T12:05:33.048579+08:00", - "hrp_version": "v4.1.0-beta" + "create_time": "2022-05-28T21:41:04.822656+08:00", + "hrp_version": "v4.1.0" } \ No newline at end of file diff --git a/examples/demo-with-py-plugin/testcases/ref_testcase.yml b/examples/demo-with-py-plugin/testcases/ref_testcase.yml index 0816481c..c0932124 100644 --- a/examples/demo-with-py-plugin/testcases/ref_testcase.yml +++ b/examples/demo-with-py-plugin/testcases/ref_testcase.yml @@ -24,7 +24,7 @@ teststeps: method: POST url: /post headers: - User-Agent: funplugin/${get_version()} + User-Agent: ${get_user_agent()} Content-Type: "application/x-www-form-urlencoded" body: "foo1=$foo1&foo2=$foo3" validate: diff --git a/examples/demo-with-py-plugin/testcases/requests.json b/examples/demo-with-py-plugin/testcases/requests.json index a05d5277..75c464f9 100644 --- a/examples/demo-with-py-plugin/testcases/requests.json +++ b/examples/demo-with-py-plugin/testcases/requests.json @@ -8,7 +8,7 @@ "expect_foo2": "config_bar2" }, "headers": { - "User-Agent": "funplugin/${get_version()}" + "User-Agent": "${get_user_agent()}" }, "base_url": "https://postman-echo.com", "verify": false, diff --git a/examples/demo-with-py-plugin/testcases/requests.yml b/examples/demo-with-py-plugin/testcases/requests.yml index 7fcfbe1e..1db4e4d1 100644 --- a/examples/demo-with-py-plugin/testcases/requests.yml +++ b/examples/demo-with-py-plugin/testcases/requests.yml @@ -6,7 +6,7 @@ config: expect_foo1: config_bar1 expect_foo2: config_bar2 headers: - User-Agent: funplugin/${get_version()} + User-Agent: ${get_user_agent()} verify: False export: ["foo3"] diff --git a/examples/demo-without-plugin/proj.json b/examples/demo-without-plugin/proj.json index 9f1070cc..cb46e886 100644 --- a/examples/demo-without-plugin/proj.json +++ b/examples/demo-without-plugin/proj.json @@ -1,6 +1,6 @@ { "project_name": "demo-without-plugin", "project_path": "/Users/debugtalk/MyProjects/HttpRunner-dev/httprunner/examples/demo-without-plugin", - "create_time": "2022-05-28T12:05:34.258157+08:00", - "hrp_version": "v4.1.0-beta" + "create_time": "2022-05-28T21:41:06.311412+08:00", + "hrp_version": "v4.1.0" } \ No newline at end of file diff --git a/hrp/internal/build/main.go b/hrp/internal/build/main.go index 72848b5b..6e7902f6 100644 --- a/hrp/internal/build/main.go +++ b/hrp/internal/build/main.go @@ -5,7 +5,6 @@ import ( _ "embed" "fmt" "io" - "io/ioutil" "os" "os/exec" "path/filepath" @@ -31,6 +30,11 @@ const ( regexGoFunctionContent = `func [\s\S]*?\n}` ) +const ( + genDebugTalkGo = "debugtalk_gen.go" + genDebugTalkPy = "debugtalk_gen.py" +) + //go:embed templates/debugtalkPythonTemplate var pyTemplate string @@ -55,7 +59,7 @@ type Regexps struct { } func (t *TemplateContent) parseGoContent(path string) error { - log.Info().Msg(fmt.Sprintf("start to parse %v", path)) + log.Info().Str("path", path).Msg("start to parse debugtalk.go") content, err := os.ReadFile(path) if err != nil { @@ -108,7 +112,7 @@ func (t *TemplateContent) parseGoContent(path string) error { func (t *TemplateContent) parsePyContent(path string) error { file, err := os.Open(path) if err != nil { - fmt.Printf("Error: %s\n", err) + log.Error().Err(err).Str("path", path).Msg("failed to open file") return err } defer file.Close() @@ -191,38 +195,34 @@ func buildGo(path string, output string) error { }, } - // create temp dir for building - tempDir, err := ioutil.TempDir("", "hrp_build") - if err != nil { - return err - } + pluginDir := filepath.Dir(path) // check go sdk in tempDir - if err := builtin.ExecCommandInDir(exec.Command("go", "version"), tempDir); err != nil { + if err := builtin.ExecCommandInDir(exec.Command("go", "version"), pluginDir); err != nil { return errors.Wrap(err, "go sdk not installed") } - // create pluginDir - pluginDir := filepath.Join(tempDir, "plugin") - if err := builtin.CreateFolder(pluginDir); err != nil { - return err - } // parse debugtalk.go in pluginDir - err = templateContent.parseGoContent(path) - if err != nil { - return err - } - // generate debugtalk.go in pluginDir - err = templateContent.genDebugTalk(filepath.Join(pluginDir, "debugtalk.go"), goTemplate) + err := templateContent.parseGoContent(path) if err != nil { return err } - // create go mod - if err := builtin.ExecCommandInDir(exec.Command("go", "mod", "init", "plugin"), pluginDir); err != nil { + // generate debugtalk.go in pluginDir + err = templateContent.genDebugTalk(filepath.Join(pluginDir, genDebugTalkGo), goTemplate) + if err != nil { return err } + // create go mod if not exists + goModFile := filepath.Join(pluginDir, "go.mod") + if !builtin.IsFilePathExists(goModFile) { + err = builtin.ExecCommandInDir(exec.Command("go", "mod", "init", "plugin"), pluginDir) + if err != nil { + return err + } + } + // download plugin dependency // funplugin version should be locked funplugin := fmt.Sprintf("github.com/httprunner/funplugin@%s", shared.Version) @@ -242,10 +242,11 @@ func buildGo(path string, output string) error { } // build plugin debugtalk.bin - if err := builtin.ExecCommandInDir(exec.Command("go", "build", "-o", outputPath, "debugtalk.go"), pluginDir); err != nil { + cmd := exec.Command("go", "build", "-o", outputPath, genDebugTalkGo, filepath.Base(path)) + if err := builtin.ExecCommandInDir(cmd, pluginDir); err != nil { return err } - log.Info().Msg(fmt.Sprintf("build %s to %s successfully", path, outputPath)) + log.Info().Str("output", outputPath).Str("plugin", path).Msg("build plugin successfully") return nil } @@ -266,9 +267,9 @@ func buildPy(path string, output string) error { // generate debugtalk.py if output == "" { dir, _ := os.Getwd() - output = filepath.Join(dir, "debugtalk_gen.py") + output = filepath.Join(dir, genDebugTalkPy) } else if builtin.IsFolderPathExists(output) { - output = filepath.Join(output, "debugtalk_gen.py") + output = filepath.Join(output, genDebugTalkPy) } err = templateContent.genDebugTalk(output, pyTemplate) if err != nil { @@ -295,7 +296,7 @@ func Run(arg string, output string) (err error) { return errors.New("type error, expected .py or .go") } if err != nil { - log.Error().Err(err).Msg(fmt.Sprintf("failed to build %s", arg)) + log.Error().Err(err).Str("arg", arg).Msg("build plugin failed") os.Exit(1) } return nil diff --git a/hrp/internal/build/main_test.go b/hrp/internal/build/main_test.go index 41eb1489..cfe1cdbf 100644 --- a/hrp/internal/build/main_test.go +++ b/hrp/internal/build/main_test.go @@ -4,23 +4,24 @@ import ( "regexp" "testing" - "github.com/httprunner/httprunner/v4/hrp/internal/builtin" - "github.com/stretchr/testify/assert" + + "github.com/httprunner/httprunner/v4/hrp/internal/builtin" ) func TestRun(t *testing.T) { - err := Run("plugin/debugtalk.go", "./debugtalk_gen.bin") + err := Run("../scaffold/templates/plugin/debugtalk.go", "./debugtalk.bin") if !assert.Nil(t, err) { t.Fatal() } - err = Run("plugin/debugtalk.py", "./debugtalk_gen.py") + genDebugTalkPy := "../scaffold/templates/plugin/debugtalk_gen.py" + err = Run("../scaffold/templates/plugin/debugtalk.py", genDebugTalkPy) if !assert.Nil(t, err) { t.Fatal() } - contentBytes, err := builtin.ReadFile("./debugtalk_gen.py") + contentBytes, err := builtin.ReadFile(genDebugTalkPy) if !assert.Nil(t, err) { t.Fatal() } diff --git a/hrp/internal/build/plugin/debugtalk.go b/hrp/internal/build/plugin/debugtalk.go deleted file mode 100644 index eb189472..00000000 --- a/hrp/internal/build/plugin/debugtalk.go +++ /dev/null @@ -1,44 +0,0 @@ -package noplugin - -import ( - "fmt" -) - -func SumTwoInt(a, b int) int { - return a + b -} - -func SumInts(args ...int) int { - var sum int - for _, arg := range args { - sum += arg - } - return sum -} - -func Sum(args ...interface{}) (interface{}, error) { - var sum float64 - for _, arg := range args { - switch v := arg.(type) { - case int: - sum += float64(v) - case float64: - sum += v - default: - return nil, fmt.Errorf("unexpected type: %T", arg) - } - } - return sum, nil -} - -func SetupHookExample(args string) string { - return fmt.Sprintf("step name: %v, setup...", args) -} - -func TeardownHookExample(args string) string { - return fmt.Sprintf("step name: %v, teardown...", args) -} - -func GetVersion() string { - return "v0.4" -} diff --git a/hrp/internal/build/templates/debugtalkGoTemplate b/hrp/internal/build/templates/debugtalkGoTemplate index 842eba1d..d5b096bc 100644 --- a/hrp/internal/build/templates/debugtalkGoTemplate +++ b/hrp/internal/build/templates/debugtalkGoTemplate @@ -1,15 +1,10 @@ +// NOTE: Generated By hrp {{ .Version }}, DO NOT EDIT! package main import ( -{{- range $import := .Imports }} - {{ $import -}} -{{ end }} + "github.com/httprunner/funplugin/fungo" ) -{{ range $function := .Functions }} -{{ $function }} -{{ end }} - func main() { {{- range $functionName := .FunctionNames }} fungo.Register("{{ $functionName }}", {{ $functionName }}) diff --git a/hrp/internal/scaffold/main.go b/hrp/internal/scaffold/main.go index 0941838d..501cc2ef 100644 --- a/hrp/internal/scaffold/main.go +++ b/hrp/internal/scaffold/main.go @@ -8,7 +8,6 @@ import ( "path/filepath" "time" - "github.com/httprunner/funplugin/shared" "github.com/pkg/errors" "github.com/rs/zerolog/log" @@ -196,24 +195,7 @@ func createGoPlugin(projectName string) error { err := CopyFile("templates/plugin/debugtalk.go", filepath.Join(projectName, "plugin", "debugtalk.go")) if err != nil { - return err - } - - // create go mod - if err := builtin.ExecCommandInDir(exec.Command("go", "mod", "init", "plugin"), pluginDir); err != nil { - return err - } - - // download plugin dependency - // funplugin version should be locked - funplugin := fmt.Sprintf("github.com/httprunner/funplugin@%s", shared.Version) - if err := builtin.ExecCommandInDir(exec.Command("go", "get", funplugin), pluginDir); err != nil { - return err - } - - // build plugin debugtalk.bin - if err := builtin.ExecCommandInDir(exec.Command("go", "build", "-o", filepath.Join("..", "debugtalk.bin"), "debugtalk.go"), pluginDir); err != nil { - return err + return errors.Wrap(err, "copy debugtalk.go failed") } return nil diff --git a/hrp/internal/build/plugin/debugtalk.py b/hrp/internal/scaffold/templates/debugtalk.py similarity index 95% rename from hrp/internal/build/plugin/debugtalk.py rename to hrp/internal/scaffold/templates/debugtalk.py index 9f4c52bc..ea48ff48 100644 --- a/hrp/internal/build/plugin/debugtalk.py +++ b/hrp/internal/scaffold/templates/debugtalk.py @@ -3,8 +3,8 @@ import time from typing import List -def get_version(): - return "v0.4" +def get_user_agent(): + return "hrp/funppy" def sleep(n_secs): diff --git a/hrp/internal/scaffold/templates/plugin/debugtalk.go b/hrp/internal/scaffold/templates/plugin/debugtalk.go index b3b39400..73deb244 100644 --- a/hrp/internal/scaffold/templates/plugin/debugtalk.go +++ b/hrp/internal/scaffold/templates/plugin/debugtalk.go @@ -2,8 +2,6 @@ package main import ( "fmt" - - "github.com/httprunner/funplugin/fungo" ) func SumTwoInt(a, b int) int { @@ -41,17 +39,6 @@ func TeardownHookExample(args string) string { return fmt.Sprintf("step name: %v, teardown...", args) } -func GetVersion() string { - return fungo.Version -} - -func main() { - fungo.Register("get_version", GetVersion) - fungo.Register("sum_ints", SumInts) - fungo.Register("sum_two_int", SumTwoInt) - fungo.Register("sum_two", SumTwoInt) - fungo.Register("sum", Sum) - fungo.Register("setup_hook_example", SetupHookExample) - fungo.Register("teardown_hook_example", TeardownHookExample) - fungo.Serve() +func GetUserAgent() string { + return "hrp/fungo" } diff --git a/hrp/internal/scaffold/templates/plugin/debugtalk.py b/hrp/internal/scaffold/templates/plugin/debugtalk.py index 9fd41120..ea48ff48 100644 --- a/hrp/internal/scaffold/templates/plugin/debugtalk.py +++ b/hrp/internal/scaffold/templates/plugin/debugtalk.py @@ -2,11 +2,9 @@ import logging import time from typing import List -import funppy - -def get_version(): - return funppy.__version__ +def get_user_agent(): + return "hrp/funppy" def sleep(n_secs): @@ -57,17 +55,3 @@ def setup_hook_example(name): def teardown_hook_example(name): logging.warning("teardown_hook_example") return f"teardown_hook_example: {name}" - - -if __name__ == "__main__": - funppy.register("get_version", get_version) - funppy.register("sum", sum) - funppy.register("sum_ints", sum_ints) - funppy.register("concatenate", concatenate) - funppy.register("sum_two_int", sum_two_int) - funppy.register("sum_two", sum_two_int) - funppy.register("sum_two_string", sum_two_string) - funppy.register("sum_strings", sum_strings) - funppy.register("setup_hook_example", setup_hook_example) - funppy.register("teardown_hook_example", teardown_hook_example) - funppy.serve() diff --git a/hrp/internal/scaffold/templates/plugin/debugtalk_gen.go b/hrp/internal/scaffold/templates/plugin/debugtalk_gen.go new file mode 100644 index 00000000..ad875187 --- /dev/null +++ b/hrp/internal/scaffold/templates/plugin/debugtalk_gen.go @@ -0,0 +1,16 @@ +// NOTE: Generated By hrp v4.1.0, DO NOT EDIT! +package main + +import ( + "github.com/httprunner/funplugin/fungo" +) + +func main() { + fungo.Register("SumTwoInt", SumTwoInt) + fungo.Register("SumInts", SumInts) + fungo.Register("Sum", Sum) + fungo.Register("SetupHookExample", SetupHookExample) + fungo.Register("TeardownHookExample", TeardownHookExample) + fungo.Register("GetUserAgent", GetUserAgent) + fungo.Serve() +} diff --git a/examples/demo-with-py-plugin/debugtalk_gen.py b/hrp/internal/scaffold/templates/plugin/debugtalk_gen.py similarity index 90% rename from examples/demo-with-py-plugin/debugtalk_gen.py rename to hrp/internal/scaffold/templates/plugin/debugtalk_gen.py index 76adcbdd..1bc989f2 100644 --- a/examples/demo-with-py-plugin/debugtalk_gen.py +++ b/hrp/internal/scaffold/templates/plugin/debugtalk_gen.py @@ -1,4 +1,4 @@ -# NOTE: Generated By hrp v4.1.0-beta, DO NOT EDIT! +# NOTE: Generated By hrp v4.1.0, DO NOT EDIT! import logging import time @@ -7,8 +7,8 @@ import funppy from typing import List -def get_version(): - return funppy.__version__ +def get_user_agent(): + return "hrp/funppy" def sleep(n_secs): @@ -62,7 +62,7 @@ def teardown_hook_example(name): if __name__ == "__main__": - funppy.register("get_version", get_version) + funppy.register("get_user_agent", get_user_agent) funppy.register("sleep", sleep) funppy.register("sum", sum) funppy.register("sum_ints", sum_ints) diff --git a/hrp/internal/scaffold/templates/testcases/demo_ref_testcase.yml b/hrp/internal/scaffold/templates/testcases/demo_ref_testcase.yml index 0816481c..c0932124 100644 --- a/hrp/internal/scaffold/templates/testcases/demo_ref_testcase.yml +++ b/hrp/internal/scaffold/templates/testcases/demo_ref_testcase.yml @@ -24,7 +24,7 @@ teststeps: method: POST url: /post headers: - User-Agent: funplugin/${get_version()} + User-Agent: ${get_user_agent()} Content-Type: "application/x-www-form-urlencoded" body: "foo1=$foo1&foo2=$foo3" validate: diff --git a/hrp/internal/scaffold/templates/testcases/demo_ref_testcase_test.py b/hrp/internal/scaffold/templates/testcases/demo_ref_testcase_test.py index ce77286e..4a57b302 100644 --- a/hrp/internal/scaffold/templates/testcases/demo_ref_testcase_test.py +++ b/hrp/internal/scaffold/templates/testcases/demo_ref_testcase_test.py @@ -43,7 +43,7 @@ class TestCaseDemoRefTestcase(HttpRunner): .post("/post") .with_headers( **{ - "User-Agent": "funplugin/${get_version()}", + "User-Agent": "${get_user_agent()}", "Content-Type": "application/x-www-form-urlencoded", } ) diff --git a/hrp/internal/scaffold/templates/testcases/demo_requests.json b/hrp/internal/scaffold/templates/testcases/demo_requests.json index a05d5277..75c464f9 100644 --- a/hrp/internal/scaffold/templates/testcases/demo_requests.json +++ b/hrp/internal/scaffold/templates/testcases/demo_requests.json @@ -8,7 +8,7 @@ "expect_foo2": "config_bar2" }, "headers": { - "User-Agent": "funplugin/${get_version()}" + "User-Agent": "${get_user_agent()}" }, "base_url": "https://postman-echo.com", "verify": false, diff --git a/hrp/internal/scaffold/templates/testcases/demo_requests.yml b/hrp/internal/scaffold/templates/testcases/demo_requests.yml index 7fcfbe1e..1db4e4d1 100644 --- a/hrp/internal/scaffold/templates/testcases/demo_requests.yml +++ b/hrp/internal/scaffold/templates/testcases/demo_requests.yml @@ -6,7 +6,7 @@ config: expect_foo1: config_bar1 expect_foo2: config_bar2 headers: - User-Agent: funplugin/${get_version()} + User-Agent: ${get_user_agent()} verify: False export: ["foo3"] diff --git a/hrp/internal/scaffold/templates/testcases/demo_requests_test.py b/hrp/internal/scaffold/templates/testcases/demo_requests_test.py index 6e05feb1..dd65d1f0 100644 --- a/hrp/internal/scaffold/templates/testcases/demo_requests_test.py +++ b/hrp/internal/scaffold/templates/testcases/demo_requests_test.py @@ -30,7 +30,7 @@ class TestCaseDemoRequests(HttpRunner): ) .get("/get") .with_params(**{"foo1": "$foo1", "foo2": "$foo2", "sum_v": "$sum_v"}) - .with_headers(**{"User-Agent": "funplugin/${get_version()}"}) + .with_headers(**{"User-Agent": "${get_user_agent()}"}) .extract() .with_jmespath("body.args.foo2", "foo3") .validate() @@ -45,7 +45,7 @@ class TestCaseDemoRequests(HttpRunner): .post("/post") .with_headers( **{ - "User-Agent": "funplugin/${get_version()}", + "User-Agent": "${get_user_agent()}", "Content-Type": "text/plain", } ) @@ -65,7 +65,7 @@ class TestCaseDemoRequests(HttpRunner): .post("/post") .with_headers( **{ - "User-Agent": "funplugin/${get_version()}", + "User-Agent": "${get_user_agent()}", "Content-Type": "application/x-www-form-urlencoded", } ) From 6d08f13a70726fc31219f1af44a3e55d84a4d886 Mon Sep 17 00:00:00 2001 From: debugtalk <mail@debugtalk.com> Date: Sat, 28 May 2022 21:44:37 +0800 Subject: [PATCH 084/109] feat: report GA event for hrp wiki --- hrp/internal/wiki/main.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/hrp/internal/wiki/main.go b/hrp/internal/wiki/main.go index 0c4cdb44..108edca6 100644 --- a/hrp/internal/wiki/main.go +++ b/hrp/internal/wiki/main.go @@ -4,9 +4,15 @@ import ( "os/exec" "github.com/rs/zerolog/log" + + "github.com/httprunner/httprunner/v4/hrp/internal/sdk" ) func OpenWiki() error { + sdk.SendEvent(sdk.EventTracking{ + Category: "OpenWiki", + Action: "hrp wiki", + }) log.Info().Msgf("%s https://httprunner.com", openCmd) return exec.Command(openCmd, "https://httprunner.com").Run() } From da608db9bb8763cb26d352db200d92ec7996ee7b Mon Sep 17 00:00:00 2001 From: debugtalk <mail@debugtalk.com> Date: Sun, 29 May 2022 00:24:27 +0800 Subject: [PATCH 085/109] change: build python plugin --- docs/CHANGELOG.md | 2 +- docs/cmd/hrp.md | 2 +- docs/cmd/hrp_boom.md | 2 +- docs/cmd/hrp_build.md | 2 +- docs/cmd/hrp_convert.md | 2 +- docs/cmd/hrp_har2case.md | 2 +- docs/cmd/hrp_pytest.md | 2 +- docs/cmd/hrp_run.md | 2 +- docs/cmd/hrp_startproject.md | 2 +- docs/cmd/hrp_wiki.md | 2 +- examples/demo-empty-project/proj.json | 2 +- examples/demo-with-go-plugin/proj.json | 2 +- examples/demo-with-py-plugin/debugtalk_gen.py | 75 +++++++++++++++++++ examples/demo-with-py-plugin/proj.json | 2 +- examples/demo-without-plugin/proj.json | 2 +- hrp/plugin.go | 33 +++----- 16 files changed, 99 insertions(+), 37 deletions(-) create mode 100644 examples/demo-with-py-plugin/debugtalk_gen.py diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 988769fb..fca6ed2d 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -1,6 +1,6 @@ # Release History -## v4.1.0 (2022-05-28) +## v4.1.0 (2022-05-29) - feat: add `wiki` sub-command to open httprunner website - feat: add `build` sub-command for function plugin diff --git a/docs/cmd/hrp.md b/docs/cmd/hrp.md index db7329f8..fdc9a0ff 100644 --- a/docs/cmd/hrp.md +++ b/docs/cmd/hrp.md @@ -38,4 +38,4 @@ Copyright 2017 debugtalk * [hrp startproject](hrp_startproject.md) - create a scaffold project * [hrp wiki](hrp_wiki.md) - visit https://httprunner.com -###### Auto generated by spf13/cobra on 28-May-2022 +###### Auto generated by spf13/cobra on 29-May-2022 diff --git a/docs/cmd/hrp_boom.md b/docs/cmd/hrp_boom.md index 6f30b821..2eb2185a 100644 --- a/docs/cmd/hrp_boom.md +++ b/docs/cmd/hrp_boom.md @@ -42,4 +42,4 @@ hrp boom [flags] * [hrp](hrp.md) - Next-Generation API Testing Solution. -###### Auto generated by spf13/cobra on 28-May-2022 +###### Auto generated by spf13/cobra on 29-May-2022 diff --git a/docs/cmd/hrp_build.md b/docs/cmd/hrp_build.md index f3b222fe..25a5f01d 100644 --- a/docs/cmd/hrp_build.md +++ b/docs/cmd/hrp_build.md @@ -28,4 +28,4 @@ hrp build $path ... [flags] * [hrp](hrp.md) - Next-Generation API Testing Solution. -###### Auto generated by spf13/cobra on 28-May-2022 +###### Auto generated by spf13/cobra on 29-May-2022 diff --git a/docs/cmd/hrp_convert.md b/docs/cmd/hrp_convert.md index c52a5348..d063a34d 100644 --- a/docs/cmd/hrp_convert.md +++ b/docs/cmd/hrp_convert.md @@ -22,4 +22,4 @@ hrp convert $path... [flags] * [hrp](hrp.md) - Next-Generation API Testing Solution. -###### Auto generated by spf13/cobra on 28-May-2022 +###### Auto generated by spf13/cobra on 29-May-2022 diff --git a/docs/cmd/hrp_har2case.md b/docs/cmd/hrp_har2case.md index 2d4cd832..0919562e 100644 --- a/docs/cmd/hrp_har2case.md +++ b/docs/cmd/hrp_har2case.md @@ -24,4 +24,4 @@ hrp har2case $har_path... [flags] * [hrp](hrp.md) - Next-Generation API Testing Solution. -###### Auto generated by spf13/cobra on 28-May-2022 +###### Auto generated by spf13/cobra on 29-May-2022 diff --git a/docs/cmd/hrp_pytest.md b/docs/cmd/hrp_pytest.md index 87c1e906..2fefe6a9 100644 --- a/docs/cmd/hrp_pytest.md +++ b/docs/cmd/hrp_pytest.md @@ -16,4 +16,4 @@ hrp pytest $path ... [flags] * [hrp](hrp.md) - Next-Generation API Testing Solution. -###### Auto generated by spf13/cobra on 28-May-2022 +###### Auto generated by spf13/cobra on 29-May-2022 diff --git a/docs/cmd/hrp_run.md b/docs/cmd/hrp_run.md index 21a42988..36cffa03 100644 --- a/docs/cmd/hrp_run.md +++ b/docs/cmd/hrp_run.md @@ -35,4 +35,4 @@ hrp run $path... [flags] * [hrp](hrp.md) - Next-Generation API Testing Solution. -###### Auto generated by spf13/cobra on 28-May-2022 +###### Auto generated by spf13/cobra on 29-May-2022 diff --git a/docs/cmd/hrp_startproject.md b/docs/cmd/hrp_startproject.md index 45c1fdb3..cd73617f 100644 --- a/docs/cmd/hrp_startproject.md +++ b/docs/cmd/hrp_startproject.md @@ -21,4 +21,4 @@ hrp startproject $project_name [flags] * [hrp](hrp.md) - Next-Generation API Testing Solution. -###### Auto generated by spf13/cobra on 28-May-2022 +###### Auto generated by spf13/cobra on 29-May-2022 diff --git a/docs/cmd/hrp_wiki.md b/docs/cmd/hrp_wiki.md index 16fdf14d..5ae6bde5 100644 --- a/docs/cmd/hrp_wiki.md +++ b/docs/cmd/hrp_wiki.md @@ -16,4 +16,4 @@ hrp wiki [flags] * [hrp](hrp.md) - Next-Generation API Testing Solution. -###### Auto generated by spf13/cobra on 28-May-2022 +###### Auto generated by spf13/cobra on 29-May-2022 diff --git a/examples/demo-empty-project/proj.json b/examples/demo-empty-project/proj.json index e35e90c0..4e27d6b1 100644 --- a/examples/demo-empty-project/proj.json +++ b/examples/demo-empty-project/proj.json @@ -1,6 +1,6 @@ { "project_name": "demo-empty-project", "project_path": "/Users/debugtalk/MyProjects/HttpRunner-dev/httprunner/examples/demo-empty-project", - "create_time": "2022-05-28T21:41:06.430799+08:00", + "create_time": "2022-05-29T00:19:34.824392+08:00", "hrp_version": "v4.1.0" } \ No newline at end of file diff --git a/examples/demo-with-go-plugin/proj.json b/examples/demo-with-go-plugin/proj.json index 9b1626b1..47fd0ada 100644 --- a/examples/demo-with-go-plugin/proj.json +++ b/examples/demo-with-go-plugin/proj.json @@ -1,6 +1,6 @@ { "project_name": "demo-with-go-plugin", "project_path": "/Users/debugtalk/MyProjects/HttpRunner-dev/httprunner/examples/demo-with-go-plugin", - "create_time": "2022-05-28T21:41:04.688435+08:00", + "create_time": "2022-05-29T00:19:33.056663+08:00", "hrp_version": "v4.1.0" } \ No newline at end of file diff --git a/examples/demo-with-py-plugin/debugtalk_gen.py b/examples/demo-with-py-plugin/debugtalk_gen.py new file mode 100644 index 00000000..1bc989f2 --- /dev/null +++ b/examples/demo-with-py-plugin/debugtalk_gen.py @@ -0,0 +1,75 @@ +# NOTE: Generated By hrp v4.1.0, DO NOT EDIT! + +import logging +import time +import funppy + +from typing import List + + +def get_user_agent(): + return "hrp/funppy" + + +def sleep(n_secs): + time.sleep(n_secs) + + +def sum(*args): + result = 0 + for arg in args: + result += arg + return result + + +def sum_ints(*args: List[int]) -> int: + result = 0 + for arg in args: + result += arg + return result + + +def sum_two_int(a: int, b: int) -> int: + return a + b + + +def sum_two_string(a: str, b: str) -> str: + return a + b + + +def sum_strings(*args: List[str]) -> str: + result = "" + for arg in args: + result += arg + return result + + +def concatenate(*args: List[str]) -> str: + result = "" + for arg in args: + result += str(arg) + return result + + +def setup_hook_example(name): + logging.warning("setup_hook_example") + return f"setup_hook_example: {name}" + + +def teardown_hook_example(name): + logging.warning("teardown_hook_example") + return f"teardown_hook_example: {name}" + + +if __name__ == "__main__": + funppy.register("get_user_agent", get_user_agent) + funppy.register("sleep", sleep) + funppy.register("sum", sum) + funppy.register("sum_ints", sum_ints) + funppy.register("sum_two_int", sum_two_int) + funppy.register("sum_two_string", sum_two_string) + funppy.register("sum_strings", sum_strings) + funppy.register("concatenate", concatenate) + funppy.register("setup_hook_example", setup_hook_example) + funppy.register("teardown_hook_example", teardown_hook_example) + funppy.serve() diff --git a/examples/demo-with-py-plugin/proj.json b/examples/demo-with-py-plugin/proj.json index ff4fa255..1c2f45d3 100644 --- a/examples/demo-with-py-plugin/proj.json +++ b/examples/demo-with-py-plugin/proj.json @@ -1,6 +1,6 @@ { "project_name": "demo-with-py-plugin", "project_path": "/Users/debugtalk/MyProjects/HttpRunner-dev/httprunner/examples/demo-with-py-plugin", - "create_time": "2022-05-28T21:41:04.822656+08:00", + "create_time": "2022-05-29T00:19:33.203339+08:00", "hrp_version": "v4.1.0" } \ No newline at end of file diff --git a/examples/demo-without-plugin/proj.json b/examples/demo-without-plugin/proj.json index cb46e886..dc1bc258 100644 --- a/examples/demo-without-plugin/proj.json +++ b/examples/demo-without-plugin/proj.json @@ -1,6 +1,6 @@ { "project_name": "demo-without-plugin", "project_path": "/Users/debugtalk/MyProjects/HttpRunner-dev/httprunner/examples/demo-without-plugin", - "create_time": "2022-05-28T21:41:06.311412+08:00", + "create_time": "2022-05-29T00:19:34.698757+08:00", "hrp_version": "v4.1.0" } \ No newline at end of file diff --git a/hrp/plugin.go b/hrp/plugin.go index eeeda537..7d546c5f 100644 --- a/hrp/plugin.go +++ b/hrp/plugin.go @@ -5,7 +5,6 @@ import ( "os" "os/signal" "path/filepath" - "strings" "syscall" "github.com/httprunner/funplugin" @@ -35,21 +34,6 @@ func initPlugin(path string, logOn bool) (plugin funplugin.IPlugin, pluginDir st // TODO: move pluginDir to funplugin pluginDir = filepath.Dir(pluginPath) - // compatible the format of debugtalk.py with v2/v3 - ext := filepath.Ext(pluginPath) - if ext == ".py" { - // skip if only debugtalk_gen.py exists - if !strings.HasSuffix(pluginPath, "debugtalk_gen.py") { - genPyPluginPath := filepath.Join(pluginDir, "debugtalk_gen.py") - err = build.Run(pluginPath, genPyPluginPath) - if err != nil { - log.Error().Err(err).Msgf(fmt.Sprintf("failed to build %s", pluginPath)) - return - } - pluginPath = genPyPluginPath - } - } - // found plugin file plugin, err = funplugin.Init(pluginPath, funplugin.WithLogOn(logOn)) if err != nil { @@ -80,7 +64,7 @@ func initPlugin(path string, logOn bool) (plugin funplugin.IPlugin, pluginDir st } func locatePlugin(path string) (pluginPath string, err error) { - // priority: hashicorp plugin (debugtalk.bin > debugtalk.py > debugtalk_gen.py) > go plugin (debugtalk.so) + // priority: hashicorp plugin (debugtalk.bin > debugtalk.py) > go plugin (debugtalk.so) pluginPath, err = locateFile(path, hashicorpGoPluginFile) if err == nil { @@ -89,12 +73,15 @@ func locatePlugin(path string) (pluginPath string, err error) { pluginPath, err = locateFile(path, debugtalkPyFile) if err == nil { - return - } - - pluginPath, err = locateFile(path, hashicorpPyPluginFile) - if err == nil { - return + // convert debugtalk.py to debugtalk_gen.py + // register funppy plugin + genPyPluginPath := filepath.Join(filepath.Dir(pluginPath), hashicorpPyPluginFile) + err = build.Run(pluginPath, genPyPluginPath) + if err != nil { + log.Error().Err(err).Str("path", pluginPath).Msg("build plugin failed") + return + } + return genPyPluginPath, nil } pluginPath, err = locateFile(path, goPluginFile) From 3d17bcecdf10edd56e71c6121aaacddb4b74c3ca Mon Sep 17 00:00:00 2001 From: debugtalk <mail@debugtalk.com> Date: Sun, 29 May 2022 00:50:20 +0800 Subject: [PATCH 086/109] refactor: rename debugtalk_gen.py to .debugtalk_gen.py --- examples/demo-empty-project/proj.json | 2 +- examples/demo-with-go-plugin/proj.json | 2 +- .../{debugtalk_gen.py => .debugtalk_gen.py} | 0 examples/demo-with-py-plugin/proj.json | 2 +- examples/demo-without-plugin/proj.json | 2 +- hrp/internal/build/main.go | 2 +- hrp/internal/build/main_test.go | 6 +- .../debugtalk_gen.py => .debugtalk_gen.py} | 0 .../templates/plugin/.debugtalk_gen.py | 75 +++++++++++++++++++ hrp/plugin.go | 11 ++- hrp/runner_test.go | 4 +- 11 files changed, 90 insertions(+), 16 deletions(-) rename examples/demo-with-py-plugin/{debugtalk_gen.py => .debugtalk_gen.py} (100%) rename hrp/internal/scaffold/templates/{plugin/debugtalk_gen.py => .debugtalk_gen.py} (100%) create mode 100644 hrp/internal/scaffold/templates/plugin/.debugtalk_gen.py diff --git a/examples/demo-empty-project/proj.json b/examples/demo-empty-project/proj.json index 4e27d6b1..2eac429f 100644 --- a/examples/demo-empty-project/proj.json +++ b/examples/demo-empty-project/proj.json @@ -1,6 +1,6 @@ { "project_name": "demo-empty-project", "project_path": "/Users/debugtalk/MyProjects/HttpRunner-dev/httprunner/examples/demo-empty-project", - "create_time": "2022-05-29T00:19:34.824392+08:00", + "create_time": "2022-05-29T00:38:43.685282+08:00", "hrp_version": "v4.1.0" } \ No newline at end of file diff --git a/examples/demo-with-go-plugin/proj.json b/examples/demo-with-go-plugin/proj.json index 47fd0ada..b5665891 100644 --- a/examples/demo-with-go-plugin/proj.json +++ b/examples/demo-with-go-plugin/proj.json @@ -1,6 +1,6 @@ { "project_name": "demo-with-go-plugin", "project_path": "/Users/debugtalk/MyProjects/HttpRunner-dev/httprunner/examples/demo-with-go-plugin", - "create_time": "2022-05-29T00:19:33.056663+08:00", + "create_time": "2022-05-29T00:38:42.384694+08:00", "hrp_version": "v4.1.0" } \ No newline at end of file diff --git a/examples/demo-with-py-plugin/debugtalk_gen.py b/examples/demo-with-py-plugin/.debugtalk_gen.py similarity index 100% rename from examples/demo-with-py-plugin/debugtalk_gen.py rename to examples/demo-with-py-plugin/.debugtalk_gen.py diff --git a/examples/demo-with-py-plugin/proj.json b/examples/demo-with-py-plugin/proj.json index 1c2f45d3..8dba7a24 100644 --- a/examples/demo-with-py-plugin/proj.json +++ b/examples/demo-with-py-plugin/proj.json @@ -1,6 +1,6 @@ { "project_name": "demo-with-py-plugin", "project_path": "/Users/debugtalk/MyProjects/HttpRunner-dev/httprunner/examples/demo-with-py-plugin", - "create_time": "2022-05-29T00:19:33.203339+08:00", + "create_time": "2022-05-29T00:38:42.523325+08:00", "hrp_version": "v4.1.0" } \ No newline at end of file diff --git a/examples/demo-without-plugin/proj.json b/examples/demo-without-plugin/proj.json index dc1bc258..6aafc4c4 100644 --- a/examples/demo-without-plugin/proj.json +++ b/examples/demo-without-plugin/proj.json @@ -1,6 +1,6 @@ { "project_name": "demo-without-plugin", "project_path": "/Users/debugtalk/MyProjects/HttpRunner-dev/httprunner/examples/demo-without-plugin", - "create_time": "2022-05-29T00:19:34.698757+08:00", + "create_time": "2022-05-29T00:38:43.556711+08:00", "hrp_version": "v4.1.0" } \ No newline at end of file diff --git a/hrp/internal/build/main.go b/hrp/internal/build/main.go index 6e7902f6..45d1912e 100644 --- a/hrp/internal/build/main.go +++ b/hrp/internal/build/main.go @@ -32,7 +32,7 @@ const ( const ( genDebugTalkGo = "debugtalk_gen.go" - genDebugTalkPy = "debugtalk_gen.py" + genDebugTalkPy = ".debugtalk_gen.py" ) //go:embed templates/debugtalkPythonTemplate diff --git a/hrp/internal/build/main_test.go b/hrp/internal/build/main_test.go index cfe1cdbf..b05fd53e 100644 --- a/hrp/internal/build/main_test.go +++ b/hrp/internal/build/main_test.go @@ -15,13 +15,13 @@ func TestRun(t *testing.T) { t.Fatal() } - genDebugTalkPy := "../scaffold/templates/plugin/debugtalk_gen.py" - err = Run("../scaffold/templates/plugin/debugtalk.py", genDebugTalkPy) + genDebugTalkPyPath := "../scaffold/templates/plugin/" + genDebugTalkPy + err = Run("../scaffold/templates/plugin/debugtalk.py", genDebugTalkPyPath) if !assert.Nil(t, err) { t.Fatal() } - contentBytes, err := builtin.ReadFile(genDebugTalkPy) + contentBytes, err := builtin.ReadFile(genDebugTalkPyPath) if !assert.Nil(t, err) { t.Fatal() } diff --git a/hrp/internal/scaffold/templates/plugin/debugtalk_gen.py b/hrp/internal/scaffold/templates/.debugtalk_gen.py similarity index 100% rename from hrp/internal/scaffold/templates/plugin/debugtalk_gen.py rename to hrp/internal/scaffold/templates/.debugtalk_gen.py diff --git a/hrp/internal/scaffold/templates/plugin/.debugtalk_gen.py b/hrp/internal/scaffold/templates/plugin/.debugtalk_gen.py new file mode 100644 index 00000000..1bc989f2 --- /dev/null +++ b/hrp/internal/scaffold/templates/plugin/.debugtalk_gen.py @@ -0,0 +1,75 @@ +# NOTE: Generated By hrp v4.1.0, DO NOT EDIT! + +import logging +import time +import funppy + +from typing import List + + +def get_user_agent(): + return "hrp/funppy" + + +def sleep(n_secs): + time.sleep(n_secs) + + +def sum(*args): + result = 0 + for arg in args: + result += arg + return result + + +def sum_ints(*args: List[int]) -> int: + result = 0 + for arg in args: + result += arg + return result + + +def sum_two_int(a: int, b: int) -> int: + return a + b + + +def sum_two_string(a: str, b: str) -> str: + return a + b + + +def sum_strings(*args: List[str]) -> str: + result = "" + for arg in args: + result += arg + return result + + +def concatenate(*args: List[str]) -> str: + result = "" + for arg in args: + result += str(arg) + return result + + +def setup_hook_example(name): + logging.warning("setup_hook_example") + return f"setup_hook_example: {name}" + + +def teardown_hook_example(name): + logging.warning("teardown_hook_example") + return f"teardown_hook_example: {name}" + + +if __name__ == "__main__": + funppy.register("get_user_agent", get_user_agent) + funppy.register("sleep", sleep) + funppy.register("sum", sum) + funppy.register("sum_ints", sum_ints) + funppy.register("sum_two_int", sum_two_int) + funppy.register("sum_two_string", sum_two_string) + funppy.register("sum_strings", sum_strings) + funppy.register("concatenate", concatenate) + funppy.register("setup_hook_example", setup_hook_example) + funppy.register("teardown_hook_example", teardown_hook_example) + funppy.serve() diff --git a/hrp/plugin.go b/hrp/plugin.go index 7d546c5f..952fbe9c 100644 --- a/hrp/plugin.go +++ b/hrp/plugin.go @@ -15,11 +15,11 @@ import ( ) const ( - goPluginFile = "debugtalk.so" // built from go plugin - hashicorpGoPluginFile = "debugtalk.bin" // built from hashicorp go plugin - hashicorpPyPluginFile = "debugtalk_gen.py" // used for hashicorp python plugin, automatically generated by HRP - debugtalkPyFile = "debugtalk.py" // write by user - projectInfoFile = "proj.json" // used for ensuring root project + goPluginFile = "debugtalk.so" // built from go plugin + hashicorpGoPluginFile = "debugtalk.bin" // built from hashicorp go plugin + hashicorpPyPluginFile = ".debugtalk_gen.py" // used for hashicorp python plugin, automatically generated by HRP + debugtalkPyFile = "debugtalk.py" // write by user + projectInfoFile = "proj.json" // used for ensuring root project ) func initPlugin(path string, logOn bool) (plugin funplugin.IPlugin, pluginDir string, err error) { @@ -73,7 +73,6 @@ func locatePlugin(path string) (pluginPath string, err error) { pluginPath, err = locateFile(path, debugtalkPyFile) if err == nil { - // convert debugtalk.py to debugtalk_gen.py // register funppy plugin genPyPluginPath := filepath.Join(filepath.Dir(pluginPath), hashicorpPyPluginFile) err = build.Run(pluginPath, genPyPluginPath) diff --git a/hrp/runner_test.go b/hrp/runner_test.go index 5a1327e2..7437c8bc 100644 --- a/hrp/runner_test.go +++ b/hrp/runner_test.go @@ -38,8 +38,8 @@ func buildHashicorpPyPlugin() { func removeHashicorpPyPlugin() { log.Info().Msg("[teardown] remove hashicorp python plugin") - // on v4.1^, running case will generate debugtalk_gen.py used by python plugin - os.Remove(templatesDir + "debugtalk_gen.py") + // on v4.1^, running case will generate .debugtalk_gen.py used by python plugin + os.Remove(templatesDir + hashicorpPyPluginFile) } func TestRunCaseWithGoPlugin(t *testing.T) { From 88f9d63a837d6b61f1c731aed6629132a9328642 Mon Sep 17 00:00:00 2001 From: debugtalk <mail@debugtalk.com> Date: Sun, 29 May 2022 10:00:07 +0800 Subject: [PATCH 087/109] change: get plugin path from funplugin --- hrp/plugin.go | 8 +++----- hrp/runner.go | 5 +++-- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/hrp/plugin.go b/hrp/plugin.go index 952fbe9c..7d00540c 100644 --- a/hrp/plugin.go +++ b/hrp/plugin.go @@ -22,17 +22,15 @@ const ( projectInfoFile = "proj.json" // used for ensuring root project ) -func initPlugin(path string, logOn bool) (plugin funplugin.IPlugin, pluginDir string, err error) { +func initPlugin(path string, logOn bool) (plugin funplugin.IPlugin, err error) { // plugin file not found if path == "" { - return nil, "", nil + return nil, nil } pluginPath, err := locatePlugin(path) if err != nil { - return nil, "", nil + return nil, nil } - // TODO: move pluginDir to funplugin - pluginDir = filepath.Dir(pluginPath) // found plugin file plugin, err = funplugin.Init(pluginPath, funplugin.WithLogOn(logOn)) diff --git a/hrp/runner.go b/hrp/runner.go index 640e7af3..66bc6a9b 100644 --- a/hrp/runner.go +++ b/hrp/runner.go @@ -5,6 +5,7 @@ import ( "net" "net/http" "net/url" + "path/filepath" "testing" "time" @@ -231,12 +232,12 @@ func (r *HRPRunner) newCaseRunner(testcase *TestCase) (*testCaseRunner, error) { } // init parser plugin - plugin, pluginDir, err := initPlugin(testcase.Config.Path, r.pluginLogOn) + plugin, err := initPlugin(testcase.Config.Path, r.pluginLogOn) if err != nil { return nil, errors.Wrap(err, "init plugin failed") } runner.parser.plugin = plugin - runner.rootDir = pluginDir + runner.rootDir = filepath.Dir(plugin.Path()) // parse testcase config if err := runner.parseConfig(); err != nil { From 80986858f9e7ff48d8765b091c25215d42aad939 Mon Sep 17 00:00:00 2001 From: debugtalk <mail@debugtalk.com> Date: Sun, 29 May 2022 12:14:20 +0800 Subject: [PATCH 088/109] refactor: relocate build plugin --- examples/demo-empty-project/proj.json | 2 +- examples/demo-with-go-plugin/proj.json | 2 +- examples/demo-with-py-plugin/proj.json | 2 +- examples/demo-without-plugin/proj.json | 2 +- hrp/boomer_test.go | 4 +- hrp/{internal/build/main.go => build.go} | 33 ++++---- .../build/main_test.go => build_test.go} | 9 ++- hrp/cmd/build.go | 4 +- hrp/internal/scaffold/main.go | 5 +- .../scaffold/templates/.debugtalk_gen.py | 75 ------------------- .../templates/build}/debugtalkGoTemplate | 0 .../templates/build}/debugtalkPythonTemplate | 0 hrp/internal/scaffold/templates/debugtalk.py | 57 -------------- hrp/plugin.go | 24 +++--- hrp/plugin_test.go | 14 ++-- hrp/runner.go | 11 ++- hrp/runner_test.go | 34 +++++---- hrp/testcase_test.go | 31 ++++---- 18 files changed, 95 insertions(+), 214 deletions(-) rename hrp/{internal/build/main.go => build.go} (90%) rename hrp/{internal/build/main_test.go => build_test.go} (73%) delete mode 100644 hrp/internal/scaffold/templates/.debugtalk_gen.py rename hrp/internal/{build/templates => scaffold/templates/build}/debugtalkGoTemplate (100%) rename hrp/internal/{build/templates => scaffold/templates/build}/debugtalkPythonTemplate (100%) delete mode 100644 hrp/internal/scaffold/templates/debugtalk.py diff --git a/examples/demo-empty-project/proj.json b/examples/demo-empty-project/proj.json index 2eac429f..8a7e8095 100644 --- a/examples/demo-empty-project/proj.json +++ b/examples/demo-empty-project/proj.json @@ -1,6 +1,6 @@ { "project_name": "demo-empty-project", "project_path": "/Users/debugtalk/MyProjects/HttpRunner-dev/httprunner/examples/demo-empty-project", - "create_time": "2022-05-29T00:38:43.685282+08:00", + "create_time": "2022-05-29T11:29:24.797507+08:00", "hrp_version": "v4.1.0" } \ No newline at end of file diff --git a/examples/demo-with-go-plugin/proj.json b/examples/demo-with-go-plugin/proj.json index b5665891..a707307e 100644 --- a/examples/demo-with-go-plugin/proj.json +++ b/examples/demo-with-go-plugin/proj.json @@ -1,6 +1,6 @@ { "project_name": "demo-with-go-plugin", "project_path": "/Users/debugtalk/MyProjects/HttpRunner-dev/httprunner/examples/demo-with-go-plugin", - "create_time": "2022-05-29T00:38:42.384694+08:00", + "create_time": "2022-05-29T11:29:23.237043+08:00", "hrp_version": "v4.1.0" } \ No newline at end of file diff --git a/examples/demo-with-py-plugin/proj.json b/examples/demo-with-py-plugin/proj.json index 8dba7a24..3a8afa50 100644 --- a/examples/demo-with-py-plugin/proj.json +++ b/examples/demo-with-py-plugin/proj.json @@ -1,6 +1,6 @@ { "project_name": "demo-with-py-plugin", "project_path": "/Users/debugtalk/MyProjects/HttpRunner-dev/httprunner/examples/demo-with-py-plugin", - "create_time": "2022-05-29T00:38:42.523325+08:00", + "create_time": "2022-05-29T11:29:23.370656+08:00", "hrp_version": "v4.1.0" } \ No newline at end of file diff --git a/examples/demo-without-plugin/proj.json b/examples/demo-without-plugin/proj.json index 6aafc4c4..8e3725e4 100644 --- a/examples/demo-without-plugin/proj.json +++ b/examples/demo-without-plugin/proj.json @@ -1,6 +1,6 @@ { "project_name": "demo-without-plugin", "project_path": "/Users/debugtalk/MyProjects/HttpRunner-dev/httprunner/examples/demo-without-plugin", - "create_time": "2022-05-29T00:38:43.556711+08:00", + "create_time": "2022-05-29T11:29:24.659348+08:00", "hrp_version": "v4.1.0" } \ No newline at end of file diff --git a/hrp/boomer_test.go b/hrp/boomer_test.go index 4edefa38..547a4618 100644 --- a/hrp/boomer_test.go +++ b/hrp/boomer_test.go @@ -25,10 +25,10 @@ func TestBoomerStandaloneRun(t *testing.T) { NewStep("TestCase3").CallRefCase(&TestCase{Config: NewConfig("TestCase3")}), }, } - testcase2 := &demoTestCaseWithPluginJSONPath + testcase2 := TestCasePath(demoTestCaseWithPluginJSONPath) b := NewBoomer(2, 1) - go b.Run(testcase1, testcase2) + go b.Run(testcase1, &testcase2) time.Sleep(5 * time.Second) b.Quit() } diff --git a/hrp/internal/build/main.go b/hrp/build.go similarity index 90% rename from hrp/internal/build/main.go rename to hrp/build.go index 45d1912e..65eded8d 100644 --- a/hrp/internal/build/main.go +++ b/hrp/build.go @@ -1,4 +1,4 @@ -package build +package hrp import ( "bufio" @@ -30,15 +30,10 @@ const ( regexGoFunctionContent = `func [\s\S]*?\n}` ) -const ( - genDebugTalkGo = "debugtalk_gen.go" - genDebugTalkPy = ".debugtalk_gen.py" -) - -//go:embed templates/debugtalkPythonTemplate +//go:embed internal/scaffold/templates/build/debugtalkPythonTemplate var pyTemplate string -//go:embed templates/debugtalkGoTemplate +//go:embed internal/scaffold/templates/build/debugtalkGoTemplate var goTemplate string type TemplateContent struct { @@ -209,7 +204,7 @@ func buildGo(path string, output string) error { } // generate debugtalk.go in pluginDir - err = templateContent.genDebugTalk(filepath.Join(pluginDir, genDebugTalkGo), goTemplate) + err = templateContent.genDebugTalk(filepath.Join(pluginDir, PluginGoSourceGenFile), goTemplate) if err != nil { return err } @@ -232,9 +227,9 @@ func buildGo(path string, output string) error { if output == "" { dir, _ := os.Getwd() - output = filepath.Join(dir, "debugtalk.bin") + output = filepath.Join(dir, PluginHashicorpGoBuiltFile) } else if builtin.IsFolderPathExists(output) { - output = filepath.Join(output, "debugtalk.bin") + output = filepath.Join(output, PluginHashicorpGoBuiltFile) } outputPath, err := filepath.Abs(output) if err != nil { @@ -242,7 +237,7 @@ func buildGo(path string, output string) error { } // build plugin debugtalk.bin - cmd := exec.Command("go", "build", "-o", outputPath, genDebugTalkGo, filepath.Base(path)) + cmd := exec.Command("go", "build", "-o", outputPath, PluginGoSourceGenFile, filepath.Base(path)) if err := builtin.ExecCommandInDir(cmd, pluginDir); err != nil { return err } @@ -267,9 +262,9 @@ func buildPy(path string, output string) error { // generate debugtalk.py if output == "" { dir, _ := os.Getwd() - output = filepath.Join(dir, genDebugTalkPy) + output = filepath.Join(dir, PluginPySourceGenFile) } else if builtin.IsFolderPathExists(output) { - output = filepath.Join(output, genDebugTalkPy) + output = filepath.Join(output, PluginPySourceGenFile) } err = templateContent.genDebugTalk(output, pyTemplate) if err != nil { @@ -285,18 +280,18 @@ func buildPy(path string, output string) error { return nil } -func Run(arg string, output string) (err error) { - ext := filepath.Ext(arg) +func BuildPlugin(path string, output string) (err error) { + ext := filepath.Ext(path) switch ext { case ".py": - err = buildPy(arg, output) + err = buildPy(path, output) case ".go": - err = buildGo(arg, output) + err = buildGo(path, output) default: return errors.New("type error, expected .py or .go") } if err != nil { - log.Error().Err(err).Str("arg", arg).Msg("build plugin failed") + log.Error().Err(err).Str("arg", path).Msg("build plugin failed") os.Exit(1) } return nil diff --git a/hrp/internal/build/main_test.go b/hrp/build_test.go similarity index 73% rename from hrp/internal/build/main_test.go rename to hrp/build_test.go index b05fd53e..c527b4ee 100644 --- a/hrp/internal/build/main_test.go +++ b/hrp/build_test.go @@ -1,6 +1,7 @@ -package build +package hrp import ( + "path/filepath" "regexp" "testing" @@ -10,13 +11,13 @@ import ( ) func TestRun(t *testing.T) { - err := Run("../scaffold/templates/plugin/debugtalk.go", "./debugtalk.bin") + err := BuildPlugin(tmpl("plugin/debugtalk.go"), "./debugtalk.bin") if !assert.Nil(t, err) { t.Fatal() } - genDebugTalkPyPath := "../scaffold/templates/plugin/" + genDebugTalkPy - err = Run("../scaffold/templates/plugin/debugtalk.py", genDebugTalkPyPath) + genDebugTalkPyPath := filepath.Join(tmpl("plugin/"), PluginPySourceGenFile) + err = BuildPlugin(tmpl("plugin/debugtalk.py"), genDebugTalkPyPath) if !assert.Nil(t, err) { t.Fatal() } diff --git a/hrp/cmd/build.go b/hrp/cmd/build.go index da174bbc..3c8848e3 100644 --- a/hrp/cmd/build.go +++ b/hrp/cmd/build.go @@ -3,7 +3,7 @@ package cmd import ( "github.com/spf13/cobra" - "github.com/httprunner/httprunner/v4/hrp/internal/build" + "github.com/httprunner/httprunner/v4/hrp" ) var buildCmd = &cobra.Command{ @@ -17,7 +17,7 @@ var buildCmd = &cobra.Command{ setLogLevel(logLevel) }, RunE: func(cmd *cobra.Command, args []string) error { - return build.Run(args[0], output) + return hrp.BuildPlugin(args[0], output) }, } diff --git a/hrp/internal/scaffold/main.go b/hrp/internal/scaffold/main.go index 501cc2ef..941ab14c 100644 --- a/hrp/internal/scaffold/main.go +++ b/hrp/internal/scaffold/main.go @@ -11,6 +11,7 @@ import ( "github.com/pkg/errors" "github.com/rs/zerolog/log" + "github.com/httprunner/httprunner/v4/hrp" "github.com/httprunner/httprunner/v4/hrp/internal/builtin" "github.com/httprunner/httprunner/v4/hrp/internal/sdk" "github.com/httprunner/httprunner/v4/hrp/internal/version" @@ -193,7 +194,7 @@ func createGoPlugin(projectName string) error { return err } err := CopyFile("templates/plugin/debugtalk.go", - filepath.Join(projectName, "plugin", "debugtalk.go")) + filepath.Join(projectName, "plugin", hrp.PluginGoSourceFile)) if err != nil { return errors.Wrap(err, "copy debugtalk.go failed") } @@ -205,7 +206,7 @@ func createPythonPlugin(projectName string) error { log.Info().Msg("start to create hashicorp python plugin") // create debugtalk.py - pluginFile := filepath.Join(projectName, "debugtalk.py") + pluginFile := filepath.Join(projectName, hrp.PluginPySourceFile) err := CopyFile("templates/plugin/debugtalk.py", pluginFile) if err != nil { return errors.Wrap(err, "copy file failed") diff --git a/hrp/internal/scaffold/templates/.debugtalk_gen.py b/hrp/internal/scaffold/templates/.debugtalk_gen.py deleted file mode 100644 index 1bc989f2..00000000 --- a/hrp/internal/scaffold/templates/.debugtalk_gen.py +++ /dev/null @@ -1,75 +0,0 @@ -# NOTE: Generated By hrp v4.1.0, DO NOT EDIT! - -import logging -import time -import funppy - -from typing import List - - -def get_user_agent(): - return "hrp/funppy" - - -def sleep(n_secs): - time.sleep(n_secs) - - -def sum(*args): - result = 0 - for arg in args: - result += arg - return result - - -def sum_ints(*args: List[int]) -> int: - result = 0 - for arg in args: - result += arg - return result - - -def sum_two_int(a: int, b: int) -> int: - return a + b - - -def sum_two_string(a: str, b: str) -> str: - return a + b - - -def sum_strings(*args: List[str]) -> str: - result = "" - for arg in args: - result += arg - return result - - -def concatenate(*args: List[str]) -> str: - result = "" - for arg in args: - result += str(arg) - return result - - -def setup_hook_example(name): - logging.warning("setup_hook_example") - return f"setup_hook_example: {name}" - - -def teardown_hook_example(name): - logging.warning("teardown_hook_example") - return f"teardown_hook_example: {name}" - - -if __name__ == "__main__": - funppy.register("get_user_agent", get_user_agent) - funppy.register("sleep", sleep) - funppy.register("sum", sum) - funppy.register("sum_ints", sum_ints) - funppy.register("sum_two_int", sum_two_int) - funppy.register("sum_two_string", sum_two_string) - funppy.register("sum_strings", sum_strings) - funppy.register("concatenate", concatenate) - funppy.register("setup_hook_example", setup_hook_example) - funppy.register("teardown_hook_example", teardown_hook_example) - funppy.serve() diff --git a/hrp/internal/build/templates/debugtalkGoTemplate b/hrp/internal/scaffold/templates/build/debugtalkGoTemplate similarity index 100% rename from hrp/internal/build/templates/debugtalkGoTemplate rename to hrp/internal/scaffold/templates/build/debugtalkGoTemplate diff --git a/hrp/internal/build/templates/debugtalkPythonTemplate b/hrp/internal/scaffold/templates/build/debugtalkPythonTemplate similarity index 100% rename from hrp/internal/build/templates/debugtalkPythonTemplate rename to hrp/internal/scaffold/templates/build/debugtalkPythonTemplate diff --git a/hrp/internal/scaffold/templates/debugtalk.py b/hrp/internal/scaffold/templates/debugtalk.py deleted file mode 100644 index ea48ff48..00000000 --- a/hrp/internal/scaffold/templates/debugtalk.py +++ /dev/null @@ -1,57 +0,0 @@ -import logging -import time -from typing import List - - -def get_user_agent(): - return "hrp/funppy" - - -def sleep(n_secs): - time.sleep(n_secs) - - -def sum(*args): - result = 0 - for arg in args: - result += arg - return result - - -def sum_ints(*args: List[int]) -> int: - result = 0 - for arg in args: - result += arg - return result - - -def sum_two_int(a: int, b: int) -> int: - return a + b - - -def sum_two_string(a: str, b: str) -> str: - return a + b - - -def sum_strings(*args: List[str]) -> str: - result = "" - for arg in args: - result += arg - return result - - -def concatenate(*args: List[str]) -> str: - result = "" - for arg in args: - result += str(arg) - return result - - -def setup_hook_example(name): - logging.warning("setup_hook_example") - return f"setup_hook_example: {name}" - - -def teardown_hook_example(name): - logging.warning("teardown_hook_example") - return f"teardown_hook_example: {name}" diff --git a/hrp/plugin.go b/hrp/plugin.go index 7d00540c..facb09ef 100644 --- a/hrp/plugin.go +++ b/hrp/plugin.go @@ -10,18 +10,20 @@ import ( "github.com/httprunner/funplugin" "github.com/rs/zerolog/log" - "github.com/httprunner/httprunner/v4/hrp/internal/build" "github.com/httprunner/httprunner/v4/hrp/internal/sdk" ) const ( - goPluginFile = "debugtalk.so" // built from go plugin - hashicorpGoPluginFile = "debugtalk.bin" // built from hashicorp go plugin - hashicorpPyPluginFile = ".debugtalk_gen.py" // used for hashicorp python plugin, automatically generated by HRP - debugtalkPyFile = "debugtalk.py" // write by user - projectInfoFile = "proj.json" // used for ensuring root project + PluginGoBuiltFile = "debugtalk.so" // built from go official plugin + PluginHashicorpGoBuiltFile = "debugtalk.bin" // built from hashicorp go plugin + PluginGoSourceFile = "debugtalk.go" // golang function plugin source file + PluginGoSourceGenFile = "debugtalk_gen.go" // generated for hashicorp go plugin + PluginPySourceFile = "debugtalk.py" // python function plugin source file + PluginPySourceGenFile = ".debugtalk_gen.py" // generated for hashicorp python plugin ) +const projectInfoFile = "proj.json" // used for ensuring root project + func initPlugin(path string, logOn bool) (plugin funplugin.IPlugin, err error) { // plugin file not found if path == "" { @@ -64,16 +66,16 @@ func initPlugin(path string, logOn bool) (plugin funplugin.IPlugin, err error) { func locatePlugin(path string) (pluginPath string, err error) { // priority: hashicorp plugin (debugtalk.bin > debugtalk.py) > go plugin (debugtalk.so) - pluginPath, err = locateFile(path, hashicorpGoPluginFile) + pluginPath, err = locateFile(path, PluginHashicorpGoBuiltFile) if err == nil { return } - pluginPath, err = locateFile(path, debugtalkPyFile) + pluginPath, err = locateFile(path, PluginPySourceFile) if err == nil { // register funppy plugin - genPyPluginPath := filepath.Join(filepath.Dir(pluginPath), hashicorpPyPluginFile) - err = build.Run(pluginPath, genPyPluginPath) + genPyPluginPath := filepath.Join(filepath.Dir(pluginPath), PluginPySourceGenFile) + err = BuildPlugin(pluginPath, genPyPluginPath) if err != nil { log.Error().Err(err).Str("path", pluginPath).Msg("build plugin failed") return @@ -81,7 +83,7 @@ func locatePlugin(path string) (pluginPath string, err error) { return genPyPluginPath, nil } - pluginPath, err = locateFile(path, goPluginFile) + pluginPath, err = locateFile(path, PluginGoBuiltFile) if err == nil { return } diff --git a/hrp/plugin_test.go b/hrp/plugin_test.go index 26e74213..27cbaa77 100644 --- a/hrp/plugin_test.go +++ b/hrp/plugin_test.go @@ -8,36 +8,36 @@ import ( func TestLocateFile(t *testing.T) { // specify target file path - _, err := locateFile(templatesDir+"plugin/debugtalk.go", "debugtalk.go") + _, err := locateFile(tmpl("plugin/debugtalk.go"), PluginGoSourceFile) if !assert.Nil(t, err) { t.Fatal() } // specify path with the same dir - _, err = locateFile(templatesDir+"plugin/debugtalk.py", "debugtalk.go") + _, err = locateFile(tmpl("plugin/debugtalk.py"), PluginGoSourceFile) if !assert.Nil(t, err) { t.Fatal() } // specify target file path dir - _, err = locateFile(templatesDir+"plugin/", "debugtalk.go") + _, err = locateFile(tmpl("plugin/"), PluginGoSourceFile) if !assert.Nil(t, err) { t.Fatal() } // specify wrong path - _, err = locateFile(".", "debugtalk.go") + _, err = locateFile(".", PluginGoSourceFile) if !assert.Error(t, err) { t.Fatal() } - _, err = locateFile("/abc", "debugtalk.go") + _, err = locateFile("/abc", PluginGoSourceFile) if !assert.Error(t, err) { t.Fatal() } } func TestLocatePythonPlugin(t *testing.T) { - _, err := locatePlugin(templatesDir + "plugin/debugtalk.py") + _, err := locatePlugin(tmpl("plugin/debugtalk.py")) if !assert.Nil(t, err) { t.Fatal() } @@ -47,7 +47,7 @@ func TestLocateGoPlugin(t *testing.T) { buildHashicorpGoPlugin() defer removeHashicorpGoPlugin() - _, err := locatePlugin(templatesDir + "debugtalk.bin") + _, err := locatePlugin(tmpl("debugtalk.bin")) if !assert.Nil(t, err) { t.Fatal() } diff --git a/hrp/runner.go b/hrp/runner.go index 66bc6a9b..8290b1bf 100644 --- a/hrp/runner.go +++ b/hrp/runner.go @@ -166,6 +166,7 @@ func (r *HRPRunner) Run(testcases ...ITestCase) error { return err } + var runErr error // run testcase one by one for _, testcase := range testCases { sessionRunner, err := r.NewSessionRunner(testcase) @@ -185,6 +186,7 @@ func (r *HRPRunner) Run(testcases ...ITestCase) error { s.appendCaseSummary(caseSummary) if err != nil { log.Error().Err(err).Msg("[Run] run testcase failed") + runErr = err break } } @@ -206,7 +208,8 @@ func (r *HRPRunner) Run(testcases ...ITestCase) error { return err } } - return nil + + return runErr } // NewSessionRunner creates a new session runner for testcase. @@ -236,8 +239,10 @@ func (r *HRPRunner) newCaseRunner(testcase *TestCase) (*testCaseRunner, error) { if err != nil { return nil, errors.Wrap(err, "init plugin failed") } - runner.parser.plugin = plugin - runner.rootDir = filepath.Dir(plugin.Path()) + if plugin != nil { + runner.parser.plugin = plugin + runner.rootDir = filepath.Dir(plugin.Path()) + } // parse testcase config if err := runner.parseConfig(); err != nil { diff --git a/hrp/runner_test.go b/hrp/runner_test.go index 7437c8bc..9c169bc1 100644 --- a/hrp/runner_test.go +++ b/hrp/runner_test.go @@ -1,20 +1,18 @@ package hrp import ( + "io/ioutil" "os" "testing" "time" "github.com/rs/zerolog/log" "github.com/stretchr/testify/assert" - - "github.com/httprunner/httprunner/v4/hrp/internal/build" - "github.com/httprunner/httprunner/v4/hrp/internal/scaffold" ) func buildHashicorpGoPlugin() { log.Info().Msg("[init] build hashicorp go plugin") - err := build.Run(templatesDir+"plugin/debugtalk.go", templatesDir+"debugtalk.bin") + err := BuildPlugin(tmpl("plugin/debugtalk.go"), tmpl("debugtalk.bin")) if err != nil { log.Error().Err(err).Msg("build hashicorp go plugin failed") os.Exit(1) @@ -23,15 +21,15 @@ func buildHashicorpGoPlugin() { func removeHashicorpGoPlugin() { log.Info().Msg("[teardown] remove hashicorp go plugin") - os.Remove(templatesDir + "debugtalk.bin") + os.Remove(tmpl("debugtalk.bin")) } func buildHashicorpPyPlugin() { log.Info().Msg("[init] prepare hashicorp python plugin") - pluginFile := templatesDir + "debugtalk.py" - err := scaffold.CopyFile("templates/plugin/debugtalk.py", pluginFile) + src, _ := ioutil.ReadFile(tmpl("plugin/debugtalk.py")) + err := ioutil.WriteFile(tmpl("debugtalk.py"), src, 0o644) if err != nil { - log.Error().Err(err).Msg("build hashicorp python plugin failed") + log.Error().Err(err).Msg("copy hashicorp python plugin failed") os.Exit(1) } } @@ -39,7 +37,8 @@ func buildHashicorpPyPlugin() { func removeHashicorpPyPlugin() { log.Info().Msg("[teardown] remove hashicorp python plugin") // on v4.1^, running case will generate .debugtalk_gen.py used by python plugin - os.Remove(templatesDir + hashicorpPyPluginFile) + os.Remove(tmpl(PluginPySourceFile)) + os.Remove(tmpl(PluginPySourceGenFile)) } func TestRunCaseWithGoPlugin(t *testing.T) { @@ -57,6 +56,7 @@ func TestRunCaseWithPythonPlugin(t *testing.T) { } func assertRunTestCases(t *testing.T) { + refCase := TestCasePath(demoTestCaseWithPluginJSONPath) testcase1 := &TestCase{ Config: NewConfig("TestCase1"). SetBaseURL("http://httpbin.org"), @@ -83,7 +83,7 @@ func assertRunTestCases(t *testing.T) { }, }, ), - NewStep("testcase1-step4").CallRefCase(&demoTestCaseWithPluginJSONPath), + NewStep("testcase1-step4").CallRefCase(&refCase), }, } testcase2 := &TestCase{ @@ -160,7 +160,8 @@ func TestRunCaseWithPluginJSON(t *testing.T) { buildHashicorpGoPlugin() defer removeHashicorpGoPlugin() - err := NewRunner(nil).Run(&demoTestCaseWithPluginJSONPath) // hrp.Run(testCase) + testCase := TestCasePath(demoTestCaseWithPluginJSONPath) + err := NewRunner(nil).Run(&testCase) // hrp.Run(testCase) if err != nil { t.Fatal() } @@ -170,7 +171,8 @@ func TestRunCaseWithPluginYAML(t *testing.T) { buildHashicorpGoPlugin() defer removeHashicorpGoPlugin() - err := NewRunner(nil).Run(&demoTestCaseWithPluginYAMLPath) // hrp.Run(testCase) + testCase := TestCasePath(demoTestCaseWithPluginYAMLPath) + err := NewRunner(nil).Run(&testCase) // hrp.Run(testCase) if err != nil { t.Fatal() } @@ -180,16 +182,18 @@ func TestRunCaseWithRefAPI(t *testing.T) { buildHashicorpGoPlugin() defer removeHashicorpGoPlugin() - err := NewRunner(nil).Run(&demoTestCaseWithRefAPIPath) + testCase := TestCasePath(demoTestCaseWithRefAPIPath) + err := NewRunner(nil).Run(&testCase) if err != nil { t.Fatal() } + refAPI := APIPath(demoAPIGETPath) testcase := &TestCase{ Config: NewConfig("TestCase"). SetBaseURL("https://postman-echo.com"), TestSteps: []IStep{ - NewStep("run referenced api").CallRefAPI(&demoAPIGETPath), + NewStep("run referenced api").CallRefAPI(&refAPI), }, } @@ -222,7 +226,7 @@ func TestLoadTestCases(t *testing.T) { } // load test cases from single file path - tc = demoTestCaseWithPluginJSONPath + tc = TestCasePath(demoTestCaseWithPluginJSONPath) testCases, err = LoadTestCases(&tc) if !assert.Nil(t, err) { t.Fatal() diff --git a/hrp/testcase_test.go b/hrp/testcase_test.go index eb6a69ae..c55642e1 100644 --- a/hrp/testcase_test.go +++ b/hrp/testcase_test.go @@ -1,6 +1,7 @@ package hrp import ( + "path/filepath" "testing" "github.com/stretchr/testify/assert" @@ -9,17 +10,21 @@ import ( ) const ( - templatesDir = "internal/scaffold/templates/" hrpExamplesDir = "../examples/hrp" ) +// tmpl returns template file path +func tmpl(relativePath string) string { + return filepath.Join("internal/scaffold/templates/", relativePath) +} + var ( - demoTestCaseWithPluginJSONPath TestCasePath = templatesDir + "testcases/demo_with_funplugin.json" - demoTestCaseWithPluginYAMLPath TestCasePath = templatesDir + "testcases/demo_with_funplugin.yaml" - demoTestCaseWithoutPluginJSONPath TestCasePath = templatesDir + "testcases/demo_without_funplugin.json" - demoTestCaseWithoutPluginYAMLPath TestCasePath = templatesDir + "testcases/demo_without_funplugin.yaml" - demoTestCaseWithRefAPIPath TestCasePath = templatesDir + "testcases/demo_ref_api.json" - demoAPIGETPath APIPath = templatesDir + "/api/get.yml" + demoTestCaseWithPluginJSONPath = tmpl("testcases/demo_with_funplugin.json") + demoTestCaseWithPluginYAMLPath = tmpl("testcases/demo_with_funplugin.yaml") + demoTestCaseWithoutPluginJSONPath = tmpl("testcases/demo_without_funplugin.json") + demoTestCaseWithoutPluginYAMLPath = tmpl("testcases/demo_without_funplugin.yaml") + demoTestCaseWithRefAPIPath = tmpl("testcases/demo_ref_api.json") + demoAPIGETPath = tmpl("/api/get.yml") ) var demoTestCaseWithThinkTimePath TestCasePath = hrpExamplesDir + "/think_time_test.json" @@ -152,21 +157,21 @@ var demoTestCaseWithoutPlugin = &TestCase{ func TestGenDemoTestCase(t *testing.T) { tCase := demoTestCaseWithPlugin.ToTCase() - err := builtin.Dump2JSON(tCase, demoTestCaseWithPluginJSONPath.GetPath()) + err := builtin.Dump2JSON(tCase, demoTestCaseWithPluginJSONPath) if err != nil { t.Fatal() } - err = builtin.Dump2YAML(tCase, demoTestCaseWithPluginYAMLPath.GetPath()) + err = builtin.Dump2YAML(tCase, demoTestCaseWithPluginYAMLPath) if err != nil { t.Fatal() } tCase = demoTestCaseWithoutPlugin.ToTCase() - err = builtin.Dump2JSON(tCase, demoTestCaseWithoutPluginJSONPath.GetPath()) + err = builtin.Dump2JSON(tCase, demoTestCaseWithoutPluginJSONPath) if err != nil { t.Fatal() } - err = builtin.Dump2YAML(tCase, demoTestCaseWithoutPluginYAMLPath.GetPath()) + err = builtin.Dump2YAML(tCase, demoTestCaseWithoutPluginYAMLPath) if err != nil { t.Fatal() } @@ -175,11 +180,11 @@ func TestGenDemoTestCase(t *testing.T) { func TestLoadCase(t *testing.T) { tcJSON := &TCase{} tcYAML := &TCase{} - err := builtin.LoadFile(demoTestCaseWithPluginJSONPath.GetPath(), tcJSON) + err := builtin.LoadFile(demoTestCaseWithPluginJSONPath, tcJSON) if !assert.NoError(t, err) { t.Fatal() } - err = builtin.LoadFile(demoTestCaseWithPluginYAMLPath.GetPath(), tcYAML) + err = builtin.LoadFile(demoTestCaseWithPluginYAMLPath, tcYAML) if !assert.NoError(t, err) { t.Fatal() } From 0db71351f3fd9f3f144000a0fdfd5a3ecf16cf50 Mon Sep 17 00:00:00 2001 From: debugtalk <mail@debugtalk.com> Date: Sun, 29 May 2022 12:43:13 +0800 Subject: [PATCH 089/109] fix: do not init go mod --- hrp/build.go | 9 --------- 1 file changed, 9 deletions(-) diff --git a/hrp/build.go b/hrp/build.go index 65eded8d..4c082e74 100644 --- a/hrp/build.go +++ b/hrp/build.go @@ -209,15 +209,6 @@ func buildGo(path string, output string) error { return err } - // create go mod if not exists - goModFile := filepath.Join(pluginDir, "go.mod") - if !builtin.IsFilePathExists(goModFile) { - err = builtin.ExecCommandInDir(exec.Command("go", "mod", "init", "plugin"), pluginDir) - if err != nil { - return err - } - } - // download plugin dependency // funplugin version should be locked funplugin := fmt.Sprintf("github.com/httprunner/funplugin@%s", shared.Version) From 51ef0636fb95e5633ecb90e4b8452d10552827bd Mon Sep 17 00:00:00 2001 From: debugtalk <mail@debugtalk.com> Date: Sun, 29 May 2022 13:28:48 +0800 Subject: [PATCH 090/109] change: relocate plugin templates --- examples/demo-empty-project/proj.json | 2 +- examples/demo-with-go-plugin/proj.json | 2 +- examples/demo-with-py-plugin/proj.json | 2 +- examples/demo-without-plugin/proj.json | 2 +- hrp/build.go | 4 ++-- .../scaffold/templates/{build => plugin}/debugtalkGoTemplate | 0 .../templates/{build => plugin}/debugtalkPythonTemplate | 0 7 files changed, 6 insertions(+), 6 deletions(-) rename hrp/internal/scaffold/templates/{build => plugin}/debugtalkGoTemplate (100%) rename hrp/internal/scaffold/templates/{build => plugin}/debugtalkPythonTemplate (100%) diff --git a/examples/demo-empty-project/proj.json b/examples/demo-empty-project/proj.json index 8a7e8095..38a8dbce 100644 --- a/examples/demo-empty-project/proj.json +++ b/examples/demo-empty-project/proj.json @@ -1,6 +1,6 @@ { "project_name": "demo-empty-project", "project_path": "/Users/debugtalk/MyProjects/HttpRunner-dev/httprunner/examples/demo-empty-project", - "create_time": "2022-05-29T11:29:24.797507+08:00", + "create_time": "2022-05-29T13:24:31.960615+08:00", "hrp_version": "v4.1.0" } \ No newline at end of file diff --git a/examples/demo-with-go-plugin/proj.json b/examples/demo-with-go-plugin/proj.json index a707307e..ea8c1534 100644 --- a/examples/demo-with-go-plugin/proj.json +++ b/examples/demo-with-go-plugin/proj.json @@ -1,6 +1,6 @@ { "project_name": "demo-with-go-plugin", "project_path": "/Users/debugtalk/MyProjects/HttpRunner-dev/httprunner/examples/demo-with-go-plugin", - "create_time": "2022-05-29T11:29:23.237043+08:00", + "create_time": "2022-05-29T13:24:29.431434+08:00", "hrp_version": "v4.1.0" } \ No newline at end of file diff --git a/examples/demo-with-py-plugin/proj.json b/examples/demo-with-py-plugin/proj.json index 3a8afa50..847d146d 100644 --- a/examples/demo-with-py-plugin/proj.json +++ b/examples/demo-with-py-plugin/proj.json @@ -1,6 +1,6 @@ { "project_name": "demo-with-py-plugin", "project_path": "/Users/debugtalk/MyProjects/HttpRunner-dev/httprunner/examples/demo-with-py-plugin", - "create_time": "2022-05-29T11:29:23.370656+08:00", + "create_time": "2022-05-29T13:24:29.566414+08:00", "hrp_version": "v4.1.0" } \ No newline at end of file diff --git a/examples/demo-without-plugin/proj.json b/examples/demo-without-plugin/proj.json index 8e3725e4..bd9b8d79 100644 --- a/examples/demo-without-plugin/proj.json +++ b/examples/demo-without-plugin/proj.json @@ -1,6 +1,6 @@ { "project_name": "demo-without-plugin", "project_path": "/Users/debugtalk/MyProjects/HttpRunner-dev/httprunner/examples/demo-without-plugin", - "create_time": "2022-05-29T11:29:24.659348+08:00", + "create_time": "2022-05-29T13:24:31.840718+08:00", "hrp_version": "v4.1.0" } \ No newline at end of file diff --git a/hrp/build.go b/hrp/build.go index 4c082e74..d77c6b3c 100644 --- a/hrp/build.go +++ b/hrp/build.go @@ -30,10 +30,10 @@ const ( regexGoFunctionContent = `func [\s\S]*?\n}` ) -//go:embed internal/scaffold/templates/build/debugtalkPythonTemplate +//go:embed internal/scaffold/templates/plugin/debugtalkPythonTemplate var pyTemplate string -//go:embed internal/scaffold/templates/build/debugtalkGoTemplate +//go:embed internal/scaffold/templates/plugin/debugtalkGoTemplate var goTemplate string type TemplateContent struct { diff --git a/hrp/internal/scaffold/templates/build/debugtalkGoTemplate b/hrp/internal/scaffold/templates/plugin/debugtalkGoTemplate similarity index 100% rename from hrp/internal/scaffold/templates/build/debugtalkGoTemplate rename to hrp/internal/scaffold/templates/plugin/debugtalkGoTemplate diff --git a/hrp/internal/scaffold/templates/build/debugtalkPythonTemplate b/hrp/internal/scaffold/templates/plugin/debugtalkPythonTemplate similarity index 100% rename from hrp/internal/scaffold/templates/build/debugtalkPythonTemplate rename to hrp/internal/scaffold/templates/plugin/debugtalkPythonTemplate From 0b4a6ba3bc0b2f93ad14f202c4059f62140dfa06 Mon Sep 17 00:00:00 2001 From: debugtalk <mail@debugtalk.com> Date: Sun, 29 May 2022 13:33:31 +0800 Subject: [PATCH 091/109] change: upgrade funplugin to v0.4.8 --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 080c56a0..198dcf26 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,7 @@ require ( github.com/go-openapi/spec v0.20.6 github.com/google/uuid v1.3.0 github.com/gorilla/websocket v1.4.1 - github.com/httprunner/funplugin v0.4.7 + github.com/httprunner/funplugin v0.4.8 github.com/jinzhu/copier v0.3.2 github.com/jmespath/go-jmespath v0.4.0 github.com/json-iterator/go v1.1.12 diff --git a/go.sum b/go.sum index 921c4995..b3891bad 100644 --- a/go.sum +++ b/go.sum @@ -253,8 +253,8 @@ github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2p github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb h1:b5rjCoWHc7eqmAS4/qyk21ZsHyb6Mxv/jykxvNTkU4M= github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= -github.com/httprunner/funplugin v0.4.7 h1:bmk84BL8oPGE/rgxCuHgPcwJtBnwDzm/ocmFY/cKcos= -github.com/httprunner/funplugin v0.4.7/go.mod h1:vPyeJIfbpGe0epZZtAV0wCn16gLY9+imSw/zfxq0Lcc= +github.com/httprunner/funplugin v0.4.8 h1:G785jrEn6EAEg2nwuPcCQUHBTgwgoaSz5qdQU4X3JpI= +github.com/httprunner/funplugin v0.4.8/go.mod h1:vPyeJIfbpGe0epZZtAV0wCn16gLY9+imSw/zfxq0Lcc= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/imkira/go-interpol v1.1.0/go.mod h1:z0h2/2T3XF8kyEPpRgJ3kmNv+C43p+I/CoI+jC3w2iA= From 8ed2f84cf5004350fb661f0acf0c890bf7162cc3 Mon Sep 17 00:00:00 2001 From: buyuxiang <347586493@qq.com> Date: Mon, 30 May 2022 17:25:36 +0800 Subject: [PATCH 092/109] bugfix: avoid to escape html --- hrp/internal/builtin/utils.go | 15 +++++++++++++-- hrp/internal/convert/converter_har.go | 6 +++--- hrp/internal/json/json.go | 1 + 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/hrp/internal/builtin/utils.go b/hrp/internal/builtin/utils.go index 09176168..8f3ca391 100644 --- a/hrp/internal/builtin/utils.go +++ b/hrp/internal/builtin/utils.go @@ -28,8 +28,19 @@ func Dump2JSON(data interface{}, path string) error { return err } log.Info().Str("path", path).Msg("dump data to json") - file, _ := json.MarshalIndent(data, "", " ") - err = os.WriteFile(path, file, 0o644) + + // init json encoder + buffer := new(bytes.Buffer) + encoder := json.NewEncoder(buffer) + encoder.SetEscapeHTML(false) + encoder.SetIndent("", " ") + + err = encoder.Encode(data) + if err != nil { + return err + } + + err = os.WriteFile(path, buffer.Bytes(), 0o644) if err != nil { log.Error().Err(err).Msg("dump json path failed") return err diff --git a/hrp/internal/convert/converter_har.go b/hrp/internal/convert/converter_har.go index d35a9031..6ee9c156 100644 --- a/hrp/internal/convert/converter_har.go +++ b/hrp/internal/convert/converter_har.go @@ -587,11 +587,11 @@ func (s *stepFromHAR) makeRequestBody(entry *Entry) error { s.Request.Body = body } else if strings.HasPrefix(mimeType, "application/x-www-form-urlencoded") { // post form - var paramsList []string + paramsMap := make(map[string]string) for _, param := range entry.Request.PostData.Params { - paramsList = append(paramsList, fmt.Sprintf("%s=%s", param.Name, param.Value)) + paramsMap[param.Name] = param.Value } - s.Request.Body = strings.Join(paramsList, "&") + s.Request.Body = paramsMap } else if strings.HasPrefix(mimeType, "text/plain") { // post raw data s.Request.Body = entry.Request.PostData.Text diff --git a/hrp/internal/json/json.go b/hrp/internal/json/json.go index 859d1e28..640946e5 100644 --- a/hrp/internal/json/json.go +++ b/hrp/internal/json/json.go @@ -12,5 +12,6 @@ var ( MarshalIndent = json.MarshalIndent Unmarshal = json.Unmarshal NewDecoder = json.NewDecoder + NewEncoder = json.NewEncoder Get = json.Get ) From 4f855de41936ee0985c68e4ebb31cc8b1388ffbe Mon Sep 17 00:00:00 2001 From: buyuxiang <347586493@qq.com> Date: Mon, 30 May 2022 20:56:11 +0800 Subject: [PATCH 093/109] bugfix: deal with empty base_url --- hrp/internal/convert/converter_har_test.go | 4 ++-- hrp/step_request.go | 5 ++++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/hrp/internal/convert/converter_har_test.go b/hrp/internal/convert/converter_har_test.go index 0d4daa11..af94c98c 100644 --- a/hrp/internal/convert/converter_har_test.go +++ b/hrp/internal/convert/converter_har_test.go @@ -118,7 +118,7 @@ func TestMakeTestCaseFromHAR(t *testing.T) { if !assert.Equal(t, map[string]interface{}{"foo1": "HDnY8", "foo2": 12.3}, tCase.TestSteps[1].Request.Body) { t.Fatal() } - if !assert.Equal(t, "foo1=HDnY8&foo2=12.3", tCase.TestSteps[2].Request.Body) { + if !assert.Equal(t, map[string]string{"foo1": "HDnY8", "foo2": "12.3"}, tCase.TestSteps[2].Request.Body) { t.Fatal() } @@ -264,7 +264,7 @@ func TestMakeRequestDataParams(t *testing.T) { t.Fatal() } - if !assert.Equal(t, "a=1&b=2", step.Request.Body) { + if !assert.Equal(t, map[string]string{"a": "1", "b": "2"}, step.Request.Body) { t.Fatal() } } diff --git a/hrp/step_request.go b/hrp/step_request.go index 39e15d74..28b174ce 100644 --- a/hrp/step_request.go +++ b/hrp/step_request.go @@ -145,7 +145,10 @@ func (r *requestBuilder) prepareUrlParams(stepVariables map[string]interface{}) log.Error().Err(err).Msg("parse request url failed") return err } - baseURL := stepVariables["base_url"].(string) + var baseURL string + if stepVariables["base_url"] != nil { + baseURL = stepVariables["base_url"].(string) + } rawUrl := buildURL(baseURL, convertString(requestUrl)) // prepare request params From 56cd77ac112a5152cfc84ad59d9adb8444440eb8 Mon Sep 17 00:00:00 2001 From: xucong053 <xucong053@outlook.com> Date: Tue, 31 May 2022 13:09:32 +0800 Subject: [PATCH 094/109] fix: failed to build debugtalk.go without go.mod --- examples/demo-empty-project/proj.json | 4 ++-- examples/demo-with-go-plugin/proj.json | 4 ++-- examples/demo-with-py-plugin/proj.json | 4 ++-- examples/demo-without-plugin/proj.json | 4 ++-- hrp/build.go | 22 +++++++++++++++++----- 5 files changed, 25 insertions(+), 13 deletions(-) diff --git a/examples/demo-empty-project/proj.json b/examples/demo-empty-project/proj.json index 38a8dbce..89891403 100644 --- a/examples/demo-empty-project/proj.json +++ b/examples/demo-empty-project/proj.json @@ -1,6 +1,6 @@ { "project_name": "demo-empty-project", - "project_path": "/Users/debugtalk/MyProjects/HttpRunner-dev/httprunner/examples/demo-empty-project", - "create_time": "2022-05-29T13:24:31.960615+08:00", + "project_path": "/Users/xxxxx/go/src/github.com/httprunner/httprunner/examples/demo-empty-project", + "create_time": "2022-05-31T13:12:05.552655+08:00", "hrp_version": "v4.1.0" } \ No newline at end of file diff --git a/examples/demo-with-go-plugin/proj.json b/examples/demo-with-go-plugin/proj.json index ea8c1534..1d013463 100644 --- a/examples/demo-with-go-plugin/proj.json +++ b/examples/demo-with-go-plugin/proj.json @@ -1,6 +1,6 @@ { "project_name": "demo-with-go-plugin", - "project_path": "/Users/debugtalk/MyProjects/HttpRunner-dev/httprunner/examples/demo-with-go-plugin", - "create_time": "2022-05-29T13:24:29.431434+08:00", + "project_path": "/Users/xxxxx/go/src/github.com/httprunner/httprunner/examples/demo-with-go-plugin", + "create_time": "2022-05-31T13:12:04.150418+08:00", "hrp_version": "v4.1.0" } \ No newline at end of file diff --git a/examples/demo-with-py-plugin/proj.json b/examples/demo-with-py-plugin/proj.json index 847d146d..ccec7c84 100644 --- a/examples/demo-with-py-plugin/proj.json +++ b/examples/demo-with-py-plugin/proj.json @@ -1,6 +1,6 @@ { "project_name": "demo-with-py-plugin", - "project_path": "/Users/debugtalk/MyProjects/HttpRunner-dev/httprunner/examples/demo-with-py-plugin", - "create_time": "2022-05-29T13:24:29.566414+08:00", + "project_path": "/Users/xxxxx/go/src/github.com/httprunner/httprunner/examples/demo-with-py-plugin", + "create_time": "2022-05-31T13:12:04.292557+08:00", "hrp_version": "v4.1.0" } \ No newline at end of file diff --git a/examples/demo-without-plugin/proj.json b/examples/demo-without-plugin/proj.json index bd9b8d79..bf08ffc7 100644 --- a/examples/demo-without-plugin/proj.json +++ b/examples/demo-without-plugin/proj.json @@ -1,6 +1,6 @@ { "project_name": "demo-without-plugin", - "project_path": "/Users/debugtalk/MyProjects/HttpRunner-dev/httprunner/examples/demo-without-plugin", - "create_time": "2022-05-29T13:24:31.840718+08:00", + "project_path": "/Users/xxxxx/go/src/github.com/httprunner/httprunner/examples/demo-without-plugin", + "create_time": "2022-05-31T13:12:05.424068+08:00", "hrp_version": "v4.1.0" } \ No newline at end of file diff --git a/hrp/build.go b/hrp/build.go index d77c6b3c..beeafd90 100644 --- a/hrp/build.go +++ b/hrp/build.go @@ -12,10 +12,10 @@ import ( "strings" "text/template" - "github.com/httprunner/funplugin/shared" "github.com/pkg/errors" "github.com/rs/zerolog/log" + "github.com/httprunner/funplugin/shared" "github.com/httprunner/httprunner/v4/hrp/internal/builtin" "github.com/httprunner/httprunner/v4/hrp/internal/version" ) @@ -209,10 +209,22 @@ func buildGo(path string, output string) error { return err } - // download plugin dependency - // funplugin version should be locked - funplugin := fmt.Sprintf("github.com/httprunner/funplugin@%s", shared.Version) - if err := builtin.ExecCommandInDir(exec.Command("go", "get", funplugin), pluginDir); err != nil { + if !builtin.IsFilePathExists(filepath.Join(pluginDir, "go.mod")) { + // create go mod + if err := builtin.ExecCommandInDir(exec.Command("go", "mod", "init", "main"), pluginDir); err != nil { + return err + } + + // download plugin dependency + // funplugin version should be locked + funplugin := fmt.Sprintf("github.com/httprunner/funplugin@%s", shared.Version) + if err := builtin.ExecCommandInDir(exec.Command("go", "get", funplugin), pluginDir); err != nil { + return err + } + } + + // add missing and remove unused modules + if err := builtin.ExecCommandInDir(exec.Command("go", "mod", "tidy"), pluginDir); err != nil { return err } From 2c2ed04b749d3b5d00ca351840d6ad41112e06eb Mon Sep 17 00:00:00 2001 From: xucong053 <xucong053@outlook.com> Date: Tue, 31 May 2022 15:07:08 +0800 Subject: [PATCH 095/109] remove project_path info from proj.json in project dir --- examples/demo-empty-project/proj.json | 3 +-- examples/demo-with-go-plugin/proj.json | 3 +-- examples/demo-with-py-plugin/proj.json | 3 +-- examples/demo-without-plugin/proj.json | 3 +-- hrp/internal/scaffold/main.go | 10 +--------- 5 files changed, 5 insertions(+), 17 deletions(-) diff --git a/examples/demo-empty-project/proj.json b/examples/demo-empty-project/proj.json index 89891403..4ce79550 100644 --- a/examples/demo-empty-project/proj.json +++ b/examples/demo-empty-project/proj.json @@ -1,6 +1,5 @@ { "project_name": "demo-empty-project", - "project_path": "/Users/xxxxx/go/src/github.com/httprunner/httprunner/examples/demo-empty-project", - "create_time": "2022-05-31T13:12:05.552655+08:00", + "create_time": "2022-05-31T15:05:51.196187+08:00", "hrp_version": "v4.1.0" } \ No newline at end of file diff --git a/examples/demo-with-go-plugin/proj.json b/examples/demo-with-go-plugin/proj.json index 1d013463..1dc616b6 100644 --- a/examples/demo-with-go-plugin/proj.json +++ b/examples/demo-with-go-plugin/proj.json @@ -1,6 +1,5 @@ { "project_name": "demo-with-go-plugin", - "project_path": "/Users/xxxxx/go/src/github.com/httprunner/httprunner/examples/demo-with-go-plugin", - "create_time": "2022-05-31T13:12:04.150418+08:00", + "create_time": "2022-05-31T15:05:49.894029+08:00", "hrp_version": "v4.1.0" } \ No newline at end of file diff --git a/examples/demo-with-py-plugin/proj.json b/examples/demo-with-py-plugin/proj.json index ccec7c84..c854eeb0 100644 --- a/examples/demo-with-py-plugin/proj.json +++ b/examples/demo-with-py-plugin/proj.json @@ -1,6 +1,5 @@ { "project_name": "demo-with-py-plugin", - "project_path": "/Users/xxxxx/go/src/github.com/httprunner/httprunner/examples/demo-with-py-plugin", - "create_time": "2022-05-31T13:12:04.292557+08:00", + "create_time": "2022-05-31T15:05:50.036068+08:00", "hrp_version": "v4.1.0" } \ No newline at end of file diff --git a/examples/demo-without-plugin/proj.json b/examples/demo-without-plugin/proj.json index bf08ffc7..afe69717 100644 --- a/examples/demo-without-plugin/proj.json +++ b/examples/demo-without-plugin/proj.json @@ -1,6 +1,5 @@ { "project_name": "demo-without-plugin", - "project_path": "/Users/xxxxx/go/src/github.com/httprunner/httprunner/examples/demo-without-plugin", - "create_time": "2022-05-31T13:12:05.424068+08:00", + "create_time": "2022-05-31T15:05:51.066376+08:00", "hrp_version": "v4.1.0" } \ No newline at end of file diff --git a/hrp/internal/scaffold/main.go b/hrp/internal/scaffold/main.go index 941ab14c..04ad8ac5 100644 --- a/hrp/internal/scaffold/main.go +++ b/hrp/internal/scaffold/main.go @@ -28,7 +28,6 @@ const ( type ProjectInfo struct { ProjectName string `json:"project_name,omitempty" yaml:"project_name,omitempty"` - ProjectPath string `json:"project_path,omitempty" yaml:"project_path,omitempty"` CreateTime time.Time `json:"create_time,omitempty" yaml:"create_time,omitempty"` Version string `json:"hrp_version,omitempty" yaml:"hrp_version,omitempty"` } @@ -78,12 +77,6 @@ func CreateScaffold(projectName string, pluginType PluginType, force bool) error os.RemoveAll(projectName) } - // get project abs path - projectPath, err := filepath.Abs(projectName) - if err != nil { - projectPath = projectName - } - // create project folders if err := builtin.CreateFolder(projectName); err != nil { return err @@ -106,13 +99,12 @@ func CreateScaffold(projectName string, pluginType PluginType, force bool) error projectInfo := &ProjectInfo{ ProjectName: filepath.Base(projectName), - ProjectPath: projectPath, CreateTime: time.Now(), Version: version.VERSION, } // dump project information to file - err = builtin.Dump2JSON(projectInfo, filepath.Join(projectName, "proj.json")) + err := builtin.Dump2JSON(projectInfo, filepath.Join(projectName, "proj.json")) if err != nil { return err } From e71df6e8414b653cc5e4721def564e64a8424342 Mon Sep 17 00:00:00 2001 From: buyuxiang <347586493@qq.com> Date: Tue, 31 May 2022 15:11:33 +0800 Subject: [PATCH 096/109] update: hrp version => v4.1.1 --- docs/CHANGELOG.md | 4 ++++ examples/demo-empty-project/proj.json | 2 +- examples/demo-with-go-plugin/proj.json | 2 +- examples/demo-with-py-plugin/.debugtalk_gen.py | 2 +- examples/demo-with-py-plugin/proj.json | 2 +- examples/demo-without-plugin/proj.json | 2 +- hrp/internal/scaffold/templates/plugin/.debugtalk_gen.py | 2 +- hrp/internal/scaffold/templates/plugin/debugtalk_gen.go | 4 ++-- hrp/internal/version/VERSION | 2 +- hrp/internal/version/init.go | 2 +- httprunner/__init__.py | 2 +- pyproject.toml | 2 +- 12 files changed, 16 insertions(+), 12 deletions(-) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index fca6ed2d..ae07bc3c 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -1,5 +1,9 @@ # Release History +## v4.1.1 (2022-05-31) +- fix: failed to build debugtalk.go without go.mod +- fix: avoid to escape from html special characters like '&' + ## v4.1.0 (2022-05-29) - feat: add `wiki` sub-command to open httprunner website diff --git a/examples/demo-empty-project/proj.json b/examples/demo-empty-project/proj.json index 38a8dbce..e2795a32 100644 --- a/examples/demo-empty-project/proj.json +++ b/examples/demo-empty-project/proj.json @@ -2,5 +2,5 @@ "project_name": "demo-empty-project", "project_path": "/Users/debugtalk/MyProjects/HttpRunner-dev/httprunner/examples/demo-empty-project", "create_time": "2022-05-29T13:24:31.960615+08:00", - "hrp_version": "v4.1.0" + "hrp_version": "v4.1.1" } \ No newline at end of file diff --git a/examples/demo-with-go-plugin/proj.json b/examples/demo-with-go-plugin/proj.json index ea8c1534..22ced179 100644 --- a/examples/demo-with-go-plugin/proj.json +++ b/examples/demo-with-go-plugin/proj.json @@ -2,5 +2,5 @@ "project_name": "demo-with-go-plugin", "project_path": "/Users/debugtalk/MyProjects/HttpRunner-dev/httprunner/examples/demo-with-go-plugin", "create_time": "2022-05-29T13:24:29.431434+08:00", - "hrp_version": "v4.1.0" + "hrp_version": "v4.1.1" } \ No newline at end of file diff --git a/examples/demo-with-py-plugin/.debugtalk_gen.py b/examples/demo-with-py-plugin/.debugtalk_gen.py index 1bc989f2..d3b72a66 100644 --- a/examples/demo-with-py-plugin/.debugtalk_gen.py +++ b/examples/demo-with-py-plugin/.debugtalk_gen.py @@ -1,4 +1,4 @@ -# NOTE: Generated By hrp v4.1.0, DO NOT EDIT! +# NOTE: Generated By hrp v4.1.1, DO NOT EDIT! import logging import time diff --git a/examples/demo-with-py-plugin/proj.json b/examples/demo-with-py-plugin/proj.json index 847d146d..1ac035ea 100644 --- a/examples/demo-with-py-plugin/proj.json +++ b/examples/demo-with-py-plugin/proj.json @@ -2,5 +2,5 @@ "project_name": "demo-with-py-plugin", "project_path": "/Users/debugtalk/MyProjects/HttpRunner-dev/httprunner/examples/demo-with-py-plugin", "create_time": "2022-05-29T13:24:29.566414+08:00", - "hrp_version": "v4.1.0" + "hrp_version": "v4.1.1" } \ No newline at end of file diff --git a/examples/demo-without-plugin/proj.json b/examples/demo-without-plugin/proj.json index bd9b8d79..87c95192 100644 --- a/examples/demo-without-plugin/proj.json +++ b/examples/demo-without-plugin/proj.json @@ -2,5 +2,5 @@ "project_name": "demo-without-plugin", "project_path": "/Users/debugtalk/MyProjects/HttpRunner-dev/httprunner/examples/demo-without-plugin", "create_time": "2022-05-29T13:24:31.840718+08:00", - "hrp_version": "v4.1.0" + "hrp_version": "v4.1.1" } \ No newline at end of file diff --git a/hrp/internal/scaffold/templates/plugin/.debugtalk_gen.py b/hrp/internal/scaffold/templates/plugin/.debugtalk_gen.py index 1bc989f2..d3b72a66 100644 --- a/hrp/internal/scaffold/templates/plugin/.debugtalk_gen.py +++ b/hrp/internal/scaffold/templates/plugin/.debugtalk_gen.py @@ -1,4 +1,4 @@ -# NOTE: Generated By hrp v4.1.0, DO NOT EDIT! +# NOTE: Generated By hrp v4.1.1, DO NOT EDIT! import logging import time diff --git a/hrp/internal/scaffold/templates/plugin/debugtalk_gen.go b/hrp/internal/scaffold/templates/plugin/debugtalk_gen.go index ad875187..1def5aa4 100644 --- a/hrp/internal/scaffold/templates/plugin/debugtalk_gen.go +++ b/hrp/internal/scaffold/templates/plugin/debugtalk_gen.go @@ -1,4 +1,4 @@ -// NOTE: Generated By hrp v4.1.0, DO NOT EDIT! +// NOTE: Generated By hrp v4.1.1, DO NOT EDIT! package main import ( @@ -12,5 +12,5 @@ func main() { fungo.Register("SetupHookExample", SetupHookExample) fungo.Register("TeardownHookExample", TeardownHookExample) fungo.Register("GetUserAgent", GetUserAgent) - fungo.Serve() + fungo.Serve() } diff --git a/hrp/internal/version/VERSION b/hrp/internal/version/VERSION index 5469c48c..4b23c7e2 100644 --- a/hrp/internal/version/VERSION +++ b/hrp/internal/version/VERSION @@ -1 +1 @@ -v4.1.0 \ No newline at end of file +v4.1.1 \ No newline at end of file diff --git a/hrp/internal/version/init.go b/hrp/internal/version/init.go index 65b56ebc..836ffca7 100644 --- a/hrp/internal/version/init.go +++ b/hrp/internal/version/init.go @@ -7,4 +7,4 @@ import ( //go:embed VERSION var VERSION string -const HttpRunnerMinVersion = "v4.0.0-beta" +const HttpRunnerMinVersion = "v4.1.0" diff --git a/httprunner/__init__.py b/httprunner/__init__.py index 562a9028..245ddbf5 100644 --- a/httprunner/__init__.py +++ b/httprunner/__init__.py @@ -1,4 +1,4 @@ -__version__ = "v4.1.0" +__version__ = "v4.1.1" __description__ = "One-stop solution for HTTP(S) testing." diff --git a/pyproject.toml b/pyproject.toml index 9f56803d..b3f49e14 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "httprunner" -version = "v4.1.0" +version = "v4.1.1" description = "One-stop solution for HTTP(S) testing." license = "Apache-2.0" readme = "README.md" From 21ac0216b3d2c3efa2b2362379fda5fd477438dd Mon Sep 17 00:00:00 2001 From: buyuxiang <347586493@qq.com> Date: Tue, 31 May 2022 16:17:50 +0800 Subject: [PATCH 097/109] del: plugin auto-generated file --- .../templates/plugin/.debugtalk_gen.py | 75 ------------------- .../templates/plugin/debugtalk_gen.go | 16 ---- 2 files changed, 91 deletions(-) delete mode 100644 hrp/internal/scaffold/templates/plugin/.debugtalk_gen.py delete mode 100644 hrp/internal/scaffold/templates/plugin/debugtalk_gen.go diff --git a/hrp/internal/scaffold/templates/plugin/.debugtalk_gen.py b/hrp/internal/scaffold/templates/plugin/.debugtalk_gen.py deleted file mode 100644 index d3b72a66..00000000 --- a/hrp/internal/scaffold/templates/plugin/.debugtalk_gen.py +++ /dev/null @@ -1,75 +0,0 @@ -# NOTE: Generated By hrp v4.1.1, DO NOT EDIT! - -import logging -import time -import funppy - -from typing import List - - -def get_user_agent(): - return "hrp/funppy" - - -def sleep(n_secs): - time.sleep(n_secs) - - -def sum(*args): - result = 0 - for arg in args: - result += arg - return result - - -def sum_ints(*args: List[int]) -> int: - result = 0 - for arg in args: - result += arg - return result - - -def sum_two_int(a: int, b: int) -> int: - return a + b - - -def sum_two_string(a: str, b: str) -> str: - return a + b - - -def sum_strings(*args: List[str]) -> str: - result = "" - for arg in args: - result += arg - return result - - -def concatenate(*args: List[str]) -> str: - result = "" - for arg in args: - result += str(arg) - return result - - -def setup_hook_example(name): - logging.warning("setup_hook_example") - return f"setup_hook_example: {name}" - - -def teardown_hook_example(name): - logging.warning("teardown_hook_example") - return f"teardown_hook_example: {name}" - - -if __name__ == "__main__": - funppy.register("get_user_agent", get_user_agent) - funppy.register("sleep", sleep) - funppy.register("sum", sum) - funppy.register("sum_ints", sum_ints) - funppy.register("sum_two_int", sum_two_int) - funppy.register("sum_two_string", sum_two_string) - funppy.register("sum_strings", sum_strings) - funppy.register("concatenate", concatenate) - funppy.register("setup_hook_example", setup_hook_example) - funppy.register("teardown_hook_example", teardown_hook_example) - funppy.serve() diff --git a/hrp/internal/scaffold/templates/plugin/debugtalk_gen.go b/hrp/internal/scaffold/templates/plugin/debugtalk_gen.go deleted file mode 100644 index 1def5aa4..00000000 --- a/hrp/internal/scaffold/templates/plugin/debugtalk_gen.go +++ /dev/null @@ -1,16 +0,0 @@ -// NOTE: Generated By hrp v4.1.1, DO NOT EDIT! -package main - -import ( - "github.com/httprunner/funplugin/fungo" -) - -func main() { - fungo.Register("SumTwoInt", SumTwoInt) - fungo.Register("SumInts", SumInts) - fungo.Register("Sum", Sum) - fungo.Register("SetupHookExample", SetupHookExample) - fungo.Register("TeardownHookExample", TeardownHookExample) - fungo.Register("GetUserAgent", GetUserAgent) - fungo.Serve() -} From 7b574796dddaa74933f14a1fa9bbd09df4e286ec Mon Sep 17 00:00:00 2001 From: xucong053 <xucong053@outlook.com> Date: Tue, 31 May 2022 16:36:42 +0800 Subject: [PATCH 098/109] fix: add ref step name info in html report --- hrp/step_testcase.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/hrp/step_testcase.go b/hrp/step_testcase.go index 47151096..e75e52e4 100644 --- a/hrp/step_testcase.go +++ b/hrp/step_testcase.go @@ -1,6 +1,7 @@ package hrp import ( + "fmt" "time" "github.com/jinzhu/copier" @@ -88,6 +89,10 @@ func (s *StepTestCaseWithOptionalArgs) Run(r *SessionRunner) (*StepResult, error return stepResult, err } summary := sessionRunner.GetSummary() + // update step names + for _, record := range summary.Records { + record.Name = fmt.Sprintf("%s - %s", stepResult.Name, record.Name) + } stepResult.Data = summary.Records // export testcase export variables stepResult.ExportVars = summary.InOut.ExportVars From a1b5e554da2d18ddf47a9096d0caefa5b892ee09 Mon Sep 17 00:00:00 2001 From: xucong053 <xucong053@outlook.com> Date: Tue, 31 May 2022 20:11:27 +0800 Subject: [PATCH 099/109] fix: failed to regenerate .debugtalk_gen.py correctly --- hrp/build.go | 16 +++------------- hrp/plugin.go | 21 +++++++++++++-------- 2 files changed, 16 insertions(+), 21 deletions(-) diff --git a/hrp/build.go b/hrp/build.go index beeafd90..4c5cb940 100644 --- a/hrp/build.go +++ b/hrp/build.go @@ -155,7 +155,7 @@ func (t *TemplateContent) parsePyContent(path string) error { } func (t *TemplateContent) genDebugTalk(path string, templ string) error { - file, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE, 0o666) + file, err := os.Create(path) if err != nil { log.Error().Err(err).Msg("open file failed") return err @@ -262,7 +262,7 @@ func buildPy(path string, output string) error { return err } - // generate debugtalk.py + // generate .debugtalk_gen.py if output == "" { dir, _ := os.Getwd() output = filepath.Join(dir, PluginPySourceGenFile) @@ -270,17 +270,7 @@ func buildPy(path string, output string) error { output = filepath.Join(output, PluginPySourceGenFile) } err = templateContent.genDebugTalk(output, pyTemplate) - if err != nil { - return err - } - - // ensure funppy in .env - _, err = builtin.EnsurePython3Venv("funppy") - if err != nil { - return err - } - - return nil + return err } func BuildPlugin(path string, output string) (err error) { diff --git a/hrp/plugin.go b/hrp/plugin.go index facb09ef..5053b2e5 100644 --- a/hrp/plugin.go +++ b/hrp/plugin.go @@ -5,6 +5,7 @@ import ( "os" "os/signal" "path/filepath" + "strings" "syscall" "github.com/httprunner/funplugin" @@ -34,6 +35,17 @@ func initPlugin(path string, logOn bool) (plugin funplugin.IPlugin, err error) { return nil, nil } + if strings.HasSuffix(pluginPath, ".py") { + // register funppy plugin + genPyPluginPath := filepath.Join(filepath.Dir(pluginPath), PluginPySourceGenFile) + err = BuildPlugin(pluginPath, genPyPluginPath) + if err != nil { + log.Error().Err(err).Str("path", pluginPath).Msg("build plugin failed") + return nil, nil + } + pluginPath = genPyPluginPath + } + // found plugin file plugin, err = funplugin.Init(pluginPath, funplugin.WithLogOn(logOn)) if err != nil { @@ -73,14 +85,7 @@ func locatePlugin(path string) (pluginPath string, err error) { pluginPath, err = locateFile(path, PluginPySourceFile) if err == nil { - // register funppy plugin - genPyPluginPath := filepath.Join(filepath.Dir(pluginPath), PluginPySourceGenFile) - err = BuildPlugin(pluginPath, genPyPluginPath) - if err != nil { - log.Error().Err(err).Str("path", pluginPath).Msg("build plugin failed") - return - } - return genPyPluginPath, nil + return } pluginPath, err = locateFile(path, PluginGoBuiltFile) From aecbdb93672e23e5df96725f8be8166cbfb2bd3c Mon Sep 17 00:00:00 2001 From: xucong053 <xucong053@outlook.com> Date: Tue, 31 May 2022 20:47:59 +0800 Subject: [PATCH 100/109] update docs --- docs/CHANGELOG.md | 4 +- .../templates/plugin/.debugtalk_gen.py | 75 +++++++++++++++++++ .../templates/plugin/debugtalk_gen.go | 16 ++++ 3 files changed, 94 insertions(+), 1 deletion(-) create mode 100644 hrp/internal/scaffold/templates/plugin/.debugtalk_gen.py create mode 100644 hrp/internal/scaffold/templates/plugin/debugtalk_gen.go diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index ae07bc3c..39922910 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -2,7 +2,9 @@ ## v4.1.1 (2022-05-31) - fix: failed to build debugtalk.go without go.mod -- fix: avoid to escape from html special characters like '&' +- fix: avoid to escape from html special characters like '&' in converted JSON testcase +- fix: display the full step name when referencing testcase in html report +- fix: failed to regenerate debugtalk_gen.go and .debugtalk_gen.py correctly ## v4.1.0 (2022-05-29) diff --git a/hrp/internal/scaffold/templates/plugin/.debugtalk_gen.py b/hrp/internal/scaffold/templates/plugin/.debugtalk_gen.py new file mode 100644 index 00000000..d3b72a66 --- /dev/null +++ b/hrp/internal/scaffold/templates/plugin/.debugtalk_gen.py @@ -0,0 +1,75 @@ +# NOTE: Generated By hrp v4.1.1, DO NOT EDIT! + +import logging +import time +import funppy + +from typing import List + + +def get_user_agent(): + return "hrp/funppy" + + +def sleep(n_secs): + time.sleep(n_secs) + + +def sum(*args): + result = 0 + for arg in args: + result += arg + return result + + +def sum_ints(*args: List[int]) -> int: + result = 0 + for arg in args: + result += arg + return result + + +def sum_two_int(a: int, b: int) -> int: + return a + b + + +def sum_two_string(a: str, b: str) -> str: + return a + b + + +def sum_strings(*args: List[str]) -> str: + result = "" + for arg in args: + result += arg + return result + + +def concatenate(*args: List[str]) -> str: + result = "" + for arg in args: + result += str(arg) + return result + + +def setup_hook_example(name): + logging.warning("setup_hook_example") + return f"setup_hook_example: {name}" + + +def teardown_hook_example(name): + logging.warning("teardown_hook_example") + return f"teardown_hook_example: {name}" + + +if __name__ == "__main__": + funppy.register("get_user_agent", get_user_agent) + funppy.register("sleep", sleep) + funppy.register("sum", sum) + funppy.register("sum_ints", sum_ints) + funppy.register("sum_two_int", sum_two_int) + funppy.register("sum_two_string", sum_two_string) + funppy.register("sum_strings", sum_strings) + funppy.register("concatenate", concatenate) + funppy.register("setup_hook_example", setup_hook_example) + funppy.register("teardown_hook_example", teardown_hook_example) + funppy.serve() diff --git a/hrp/internal/scaffold/templates/plugin/debugtalk_gen.go b/hrp/internal/scaffold/templates/plugin/debugtalk_gen.go new file mode 100644 index 00000000..a2339a42 --- /dev/null +++ b/hrp/internal/scaffold/templates/plugin/debugtalk_gen.go @@ -0,0 +1,16 @@ +// NOTE: Generated By hrp v4.1.1, DO NOT EDIT! +package main + +import ( + "github.com/httprunner/funplugin/fungo" +) + +func main() { + fungo.Register("SumTwoInt", SumTwoInt) + fungo.Register("SumInts", SumInts) + fungo.Register("Sum", Sum) + fungo.Register("SetupHookExample", SetupHookExample) + fungo.Register("TeardownHookExample", TeardownHookExample) + fungo.Register("GetUserAgent", GetUserAgent) + fungo.Serve() +} From 174f07bac0cecb7a2db835a23776dbb2887eadb7 Mon Sep 17 00:00:00 2001 From: debugtalk <mail@debugtalk.com> Date: Fri, 3 Jun 2022 23:16:51 +0800 Subject: [PATCH 101/109] bump version to v4.1.2 --- hrp/internal/version/VERSION | 2 +- httprunner/__init__.py | 2 +- pyproject.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/hrp/internal/version/VERSION b/hrp/internal/version/VERSION index 4b23c7e2..b572ab0b 100644 --- a/hrp/internal/version/VERSION +++ b/hrp/internal/version/VERSION @@ -1 +1 @@ -v4.1.1 \ No newline at end of file +v4.1.2 \ No newline at end of file diff --git a/httprunner/__init__.py b/httprunner/__init__.py index 245ddbf5..aba58277 100644 --- a/httprunner/__init__.py +++ b/httprunner/__init__.py @@ -1,4 +1,4 @@ -__version__ = "v4.1.1" +__version__ = "v4.1.2" __description__ = "One-stop solution for HTTP(S) testing." diff --git a/pyproject.toml b/pyproject.toml index b3f49e14..b1a8da31 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "httprunner" -version = "v4.1.1" +version = "v4.1.2" description = "One-stop solution for HTTP(S) testing." license = "Apache-2.0" readme = "README.md" From f1fd4a518d2f58a29836402d6e5ffca5ca5f9940 Mon Sep 17 00:00:00 2001 From: debugtalk <mail@debugtalk.com> Date: Fri, 3 Jun 2022 23:18:58 +0800 Subject: [PATCH 102/109] fix #1331: use str_eq to assert string and digit equality --- docs/CHANGELOG.md | 5 +++++ hrp/internal/builtin/assertion.go | 7 +++++++ hrp/internal/builtin/assertion_test.go | 23 ++++++++++++++++++++++- hrp/step_request.go | 11 +++++++++++ hrp/tests/function_test.go | 2 +- 5 files changed, 46 insertions(+), 2 deletions(-) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 39922910..5fcde733 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -1,6 +1,11 @@ # Release History +## v4.1.2 (2022-06-03) + +- fix #1331: use `str_eq` to assert string and digit equality + ## v4.1.1 (2022-05-31) + - fix: failed to build debugtalk.go without go.mod - fix: avoid to escape from html special characters like '&' in converted JSON testcase - fix: display the full step name when referencing testcase in html report diff --git a/hrp/internal/builtin/assertion.go b/hrp/internal/builtin/assertion.go index 96fd4310..20473727 100644 --- a/hrp/internal/builtin/assertion.go +++ b/hrp/internal/builtin/assertion.go @@ -45,6 +45,7 @@ var Assertions = map[string]func(t assert.TestingT, actual interface{}, expected "contained_by": ContainedBy, "str_eq": StringEqual, "string_equals": StringEqual, + "equal_fold": EqualFold, "regex_match": RegexMatch, } @@ -157,6 +158,12 @@ func ContainedBy(t assert.TestingT, actual, expected interface{}, msgAndArgs ... } func StringEqual(t assert.TestingT, actual, expected interface{}, msgAndArgs ...interface{}) bool { + a := fmt.Sprintf("%v", actual) + e := fmt.Sprintf("%v", expected) + return assert.True(t, a == e, msgAndArgs) +} + +func EqualFold(t assert.TestingT, actual, expected interface{}, msgAndArgs ...interface{}) bool { if !assert.IsType(t, "string", actual, msgAndArgs) { return false } diff --git a/hrp/internal/builtin/assertion_test.go b/hrp/internal/builtin/assertion_test.go index aa7d4bde..3a2ebc89 100644 --- a/hrp/internal/builtin/assertion_test.go +++ b/hrp/internal/builtin/assertion_test.go @@ -158,6 +158,27 @@ func TestContainedBy(t *testing.T) { } func TestStringEqual(t *testing.T) { + testData := []struct { + raw interface{} + expected interface{} + }{ + {"abcd", "abcd"}, + {"0", 0}, + {"123", 123}, + // {"123.0", 123.0}, // FIXME + {"12.3", 12.3}, + {"-12.3", -12.3}, + {"-123", -123}, + } + + for _, data := range testData { + if !assert.True(t, StringEqual(t, data.raw, data.expected)) { + t.Fatal() + } + } +} + +func TestEqualFold(t *testing.T) { testData := []struct { raw interface{} expected interface{} @@ -168,7 +189,7 @@ func TestStringEqual(t *testing.T) { } for _, data := range testData { - if !assert.True(t, StringEqual(t, data.raw, data.expected)) { + if !assert.True(t, EqualFold(t, data.raw, data.expected)) { t.Fatal() } } diff --git a/hrp/step_request.go b/hrp/step_request.go index 28b174ce..f890a93e 100644 --- a/hrp/step_request.go +++ b/hrp/step_request.go @@ -1065,6 +1065,17 @@ func (s *StepRequestValidation) AssertStringEqual(jmesPath string, expected inte return s } +func (s *StepRequestValidation) AssertEqualFold(jmesPath string, expected interface{}, msg string) *StepRequestValidation { + v := Validator{ + Check: jmesPath, + Assert: "equal_fold", + Expect: expected, + Message: msg, + } + s.step.Validators = append(s.step.Validators, v) + return s +} + func (s *StepRequestValidation) AssertLengthLessOrEquals(jmesPath string, expected interface{}, msg string) *StepRequestValidation { v := Validator{ Check: jmesPath, diff --git a/hrp/tests/function_test.go b/hrp/tests/function_test.go index c6acc493..a4cc5d01 100644 --- a/hrp/tests/function_test.go +++ b/hrp/tests/function_test.go @@ -28,7 +28,7 @@ func TestCaseCallFunction(t *testing.T) { AssertLengthEqual("body.args.foo1", 5, "check args foo1"). AssertEqual("body.args.foo2", "12.3", "check args foo2"). AssertTypeMatch("body.args.foo3", "str", "check args foo3 is type string"). - AssertStringEqual("body.args.foo3", "foo3", "check args foo3 case-insensitivity"). + AssertEqualFold("body.args.foo3", "foo3", "check args foo3 case-insensitivity"). AssertContains("body.args.foo3", "Foo", "check contains "). AssertContainedBy("body.args.foo3", "this is Foo3 test", "check contained by"), // notice: request params value will be converted to string hrp.NewStep("post json data with functions"). From df212161a23816b5083e3053669b7c7b0af38a99 Mon Sep 17 00:00:00 2001 From: debugtalk <mail@debugtalk.com> Date: Sun, 5 Jun 2022 09:39:45 +0800 Subject: [PATCH 103/109] fix #1336: extract package with tar in Windows --- docs/CHANGELOG.md | 1 + scripts/install.sh | 25 +++---------------------- 2 files changed, 4 insertions(+), 22 deletions(-) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 5fcde733..3d5edf9b 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -3,6 +3,7 @@ ## v4.1.2 (2022-06-03) - fix #1331: use `str_eq` to assert string and digit equality +- fix #1336: extract package with tar in Windows ## v4.1.1 (2022-05-31) diff --git a/scripts/install.sh b/scripts/install.sh index c16f1c05..84f8392b 100644 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -36,26 +36,6 @@ function get_arch() { echo "$arch" } -function get_pkg_suffix() { - os=$1 - if [ "$os" == "windows" ]; then - echo ".zip" - else - echo ".tar.gz" - fi -} - -function extract_pkg() { - pkg=$1 - if [[ $pkg == *.zip ]]; then # windows - echo "$ unzip -o $pkg -d ." - unzip -o $pkg -d . - else - echo "$ tar -xzf $pkg" - tar -xzf "$pkg" - fi -} - function main() { echoInfo "Detect target hrp package..." version=$(get_latest_version) @@ -69,7 +49,7 @@ function main() { echo "Current OS: $os" arch=$(get_arch) echo "Current ARCH: $arch" - pkg_suffix=$(get_pkg_suffix $os) + pkg_suffix=".tar.gz" pkg="hrp-$version-$os-$arch$pkg_suffix" # download from aliyun OSS @@ -98,7 +78,8 @@ function main() { echo echoInfo "Extracting..." - extract_pkg "$pkg" + echo "$ tar -xzf $pkg" + tar -xzf "$pkg" echo "$ ls -lh" ls -lh echo From baaf2b8335aa6eb82e8afd2f6327196ee7938fdb Mon Sep 17 00:00:00 2001 From: debugtalk <mail@debugtalk.com> Date: Sun, 5 Jun 2022 20:48:26 +0800 Subject: [PATCH 104/109] fix: install package on MinGW64 --- docs/CHANGELOG.md | 1 + scripts/install.sh | 31 ++++++++++++++++++++++--------- 2 files changed, 23 insertions(+), 9 deletions(-) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 3d5edf9b..57e6cbf9 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -4,6 +4,7 @@ - fix #1331: use `str_eq` to assert string and digit equality - fix #1336: extract package with tar in Windows +- fix: install package on MinGW64 ## v4.1.1 (2022-05-31) diff --git a/scripts/install.sh b/scripts/install.sh index 84f8392b..89b28ee3 100644 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -44,24 +44,37 @@ function main() { echo "$version" exit 1 fi + echo "Latest version: $version" os=$(get_os) echo "Current OS: $os" + if [[ $os == mingw* ]]; then + echoWarn "Current OS is MinGW, try to use windows package" + os="windows" + fi + arch=$(get_arch) echo "Current ARCH: $arch" pkg_suffix=".tar.gz" pkg="hrp-$version-$os-$arch$pkg_suffix" + echo "Download package: $pkg" - # download from aliyun OSS - url="https://httprunner.oss-cn-beijing.aliyuncs.com/$pkg" - if ! curl --output /dev/null --silent --head --fail "$url"; then - # aliyun OSS url is invalid, try to download from github - version=$(get_latest_version) - pkg="hrp-$version-$os-$arch$pkg_suffix" - url="https://github.com/httprunner/httprunner/releases/download/$version/$pkg" + # download from aliyun OSS or github packages + aliyun_oss_url="https://httprunner.oss-cn-beijing.aliyuncs.com/$pkg" + github_url="https://github.com/httprunner/httprunner/releases/download/$version/$pkg" + valid_flag=false + for url in "$aliyun_oss_url" "$github_url"; do + if curl --output /dev/null --silent --head --fail "$url"; then + valid_flag=true + break + fi + echoWarn "Invalid download url: $url" + done + + if [[ "$valid_flag" == false ]]; then + echoError "No available download url found, exit!" + exit 1 fi - - echo "Latest version: $version" echo "Download url: $url" echo From 15f744bb30feb912d0d0f06ce5a79d737510c183 Mon Sep 17 00:00:00 2001 From: debugtalk <mail@debugtalk.com> Date: Sun, 5 Jun 2022 20:58:53 +0800 Subject: [PATCH 105/109] change: remove hrp har2case, replace with hrp convert --- docs/CHANGELOG.md | 3 ++- hrp/cmd/convert.go | 2 +- hrp/cmd/har2case.go | 51 --------------------------------------------- 3 files changed, 3 insertions(+), 53 deletions(-) delete mode 100644 hrp/cmd/har2case.go diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 57e6cbf9..fdb14855 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -1,10 +1,11 @@ # Release History -## v4.1.2 (2022-06-03) +## v4.1.2 (2022-06-05) - fix #1331: use `str_eq` to assert string and digit equality - fix #1336: extract package with tar in Windows - fix: install package on MinGW64 +- change: remove `hrp har2case`, replace with `hrp convert` ## v4.1.1 (2022-05-31) diff --git a/hrp/cmd/convert.go b/hrp/cmd/convert.go index a4c8d663..83ba88bb 100644 --- a/hrp/cmd/convert.go +++ b/hrp/cmd/convert.go @@ -57,5 +57,5 @@ func init() { convertCmd.Flags().BoolVar(&toJSONFlag, "to-json", false, "convert to JSON scripts (default)") convertCmd.Flags().BoolVar(&toYAMLFlag, "to-yaml", false, "convert to YAML scripts") convertCmd.Flags().StringVarP(&outputDir, "output-dir", "d", "", "specify output directory, default to the same dir with har file") - convertCmd.Flags().StringVarP(&profilePath, "profile", "p", "", "specify profile path to override headers (except for auto-generated headers) and cookies") + convertCmd.Flags().StringVarP(&profilePath, "profile", "p", "", "specify profile path to override headers and cookies") } diff --git a/hrp/cmd/har2case.go b/hrp/cmd/har2case.go deleted file mode 100644 index 7d6f1994..00000000 --- a/hrp/cmd/har2case.go +++ /dev/null @@ -1,51 +0,0 @@ -package cmd - -import ( - "errors" - - "github.com/spf13/cobra" - - "github.com/httprunner/httprunner/v4/hrp/internal/convert" -) - -// har2caseCmd represents the har2case command -var har2caseCmd = &cobra.Command{ - Use: "har2case $har_path...", - Short: "convert HAR to json/yaml testcase files", - Long: `convert HAR to json/yaml testcase files`, - Args: cobra.MinimumNArgs(1), - PreRun: func(cmd *cobra.Command, args []string) { - setLogLevel(logLevel) - }, - RunE: func(cmd *cobra.Command, args []string) error { - var flagCount int - var har2caseOutputType convert.OutputType - if har2caseGenJSONFlag { - flagCount++ - } - if har2caseGenYAMLFlag { - flagCount++ - har2caseOutputType = convert.OutputTypeYAML - } - if flagCount > 1 { - return errors.New("please specify at most one conversion flag") - } - convert.Run(har2caseOutputType, har2caseOutputDir, har2caseProfilePath, args) - return nil - }, -} - -var ( - har2caseGenJSONFlag bool - har2caseGenYAMLFlag bool - har2caseOutputDir string - har2caseProfilePath string -) - -func init() { - rootCmd.AddCommand(har2caseCmd) - har2caseCmd.Flags().BoolVarP(&har2caseGenJSONFlag, "to-json", "j", false, "convert to JSON format (default)") - har2caseCmd.Flags().BoolVarP(&har2caseGenYAMLFlag, "to-yaml", "y", false, "convert to YAML format") - har2caseCmd.Flags().StringVarP(&har2caseOutputDir, "output-dir", "d", "", "specify output directory, default to the same dir with har file") - har2caseCmd.Flags().StringVarP(&har2caseProfilePath, "profile", "p", "", "specify profile path to override headers and cookies") -} From 0d7fbd816f3e7c029267b8aadded84d8687a4991 Mon Sep 17 00:00:00 2001 From: debugtalk <mail@debugtalk.com> Date: Sun, 5 Jun 2022 21:37:27 +0800 Subject: [PATCH 106/109] change: update docs for group chat --- README.en.md | 2 +- README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.en.md b/README.en.md index e9a096ee..d8d22907 100644 --- a/README.en.md +++ b/README.en.md @@ -115,7 +115,7 @@ Use "hrp [command] --help" for more information about a command. <img src="https://httprunner.com/image/qrcode.png" alt="HttpRunner" width="400"> -如果你期望加入 HttpRunner 核心用户群,请填写[用户调研问卷][survey]并留下你的联系方式,作者将拉你进群。 +如果你期望加入 HttpRunner 用户群,请看这里:[HttpRunner v4 用户交流群,它来啦!](https://httprunner.com/blog/join-chat-group) [HttpRunner]: https://github.com/httprunner/httprunner [boomer]: https://github.com/myzhan/boomer diff --git a/README.md b/README.md index 6aa1b57a..1097dc07 100644 --- a/README.md +++ b/README.md @@ -124,7 +124,7 @@ HttpRunner is in Sentry Sponsored plan. <img src="https://httprunner.com/image/qrcode.png" alt="HttpRunner" width="400"> -如果你期望加入 HttpRunner 核心用户群,请填写[用户调研问卷][survey]并留下你的联系方式,作者将拉你进群。 +如果你期望加入 HttpRunner 用户群,请看这里:[HttpRunner v4 用户交流群,它来啦!](https://httprunner.com/blog/join-chat-group) [HttpRunner]: https://github.com/httprunner/httprunner [boomer]: https://github.com/myzhan/boomer From fff514ec60b973aa363658025c8a55b60943c91f Mon Sep 17 00:00:00 2001 From: debugtalk <mail@debugtalk.com> Date: Sun, 5 Jun 2022 21:37:49 +0800 Subject: [PATCH 107/109] fix: unzip package for windows --- scripts/install.sh | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/scripts/install.sh b/scripts/install.sh index 89b28ee3..845a669b 100644 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -91,8 +91,13 @@ function main() { echo echoInfo "Extracting..." - echo "$ tar -xzf $pkg" - tar -xzf "$pkg" + if [[ $os == windows ]]; then # windows + echo "$ unzip -o $pkg -d ." + unzip -o $pkg -d . + else + echo "$ tar -xzf $pkg" + tar -xzf "$pkg" + fi echo "$ ls -lh" ls -lh echo From da5fa1c88c03b63f67b342d4c4bc87db87258c6a Mon Sep 17 00:00:00 2001 From: debugtalk <mail@debugtalk.com> Date: Sun, 5 Jun 2022 21:39:04 +0800 Subject: [PATCH 108/109] fix #1336: extract package in Windows --- docs/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index fdb14855..4469315a 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -3,7 +3,7 @@ ## v4.1.2 (2022-06-05) - fix #1331: use `str_eq` to assert string and digit equality -- fix #1336: extract package with tar in Windows +- fix #1336: extract package in Windows - fix: install package on MinGW64 - change: remove `hrp har2case`, replace with `hrp convert` From 46a314965478ec415897ad36acc8f4bf9bd110c7 Mon Sep 17 00:00:00 2001 From: xucong053 <xucong053@outlook.com> Date: Wed, 8 Jun 2022 17:18:01 +0800 Subject: [PATCH 109/109] fix: failed to load overall pick_order strategy in parameters_setting --- examples/hrp/parameters_test.json | 4 ++-- examples/hrp/parameters_test.yaml | 2 +- hrp/parameters.go | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/examples/hrp/parameters_test.json b/examples/hrp/parameters_test.json index 9944599e..4af949ed 100644 --- a/examples/hrp/parameters_test.json +++ b/examples/hrp/parameters_test.json @@ -9,14 +9,14 @@ "username-password": "${parameterize($file)}" }, "parameters_setting": { + "pick_order": "random", "strategies": { "user_agent": { "name": "user-identity", "pick_order": "sequential" }, "username-password": { - "name": "user-info", - "pick_order": "random" + "name": "user-info" } }, "limit": 6 diff --git a/examples/hrp/parameters_test.yaml b/examples/hrp/parameters_test.yaml index aaaf9b41..23fd8c01 100644 --- a/examples/hrp/parameters_test.yaml +++ b/examples/hrp/parameters_test.yaml @@ -4,13 +4,13 @@ config: user_agent: [ "iOS/10.1", "iOS/10.2" ] username-password: ${parameterize($file)} parameters_setting: + pick_order: "random" strategies: user_agent: name: "user-identity" pick_order: "sequential" username-password: name: "user-info" - pick_order: "random" limit: 6 variables: app_version: v1 diff --git a/hrp/parameters.go b/hrp/parameters.go index 376232fa..13d1b679 100644 --- a/hrp/parameters.go +++ b/hrp/parameters.go @@ -12,7 +12,7 @@ import ( ) type TParamsConfig struct { - PickOrder iteratorPickOrder `json:"strategy,omitempty" yaml:"strategy,omitempty"` // overall pick-order strategy + PickOrder iteratorPickOrder `json:"pick_order,omitempty" yaml:"pick_order,omitempty"` // overall pick-order strategy Strategies map[string]iteratorStrategy `json:"strategies,omitempty" yaml:"strategies,omitempty"` // individual strategies for each parameters Limit int `json:"limit,omitempty" yaml:"limit,omitempty"` } @@ -69,7 +69,7 @@ func newParametersIterator(parameters map[string]Parameters, config *TParamsConf for paramName := range parameters { // check parameter individual pick order strategy strategy, ok := config.Strategies[paramName] - if !ok { + if !ok || strategy.PickOrder == "" { // default to overall pick order strategy strategy.PickOrder = config.PickOrder }