{"action":"created","comment":{"url":"https://api.github.com/repos/elastic/beats/pulls/comments/202332099","pull_request_review_id":137013868,"id":202332099,"node_id":"MDI0OlB1bGxSZXF1ZXN0UmV2aWV3Q29tbWVudDIwMjMzMjA5OQ==","diff_hunk":"@@ -0,0 +1,168 @@\n+// Licensed to Elasticsearch B.V. under one or more contributor\n+// license agreements. See the NOTICE file distributed with\n+// this work for additional information regarding copyright\n+// ownership. Elasticsearch B.V. licenses this file to you under\n+// the Apache License, Version 2.0 (the \"License\"); you may\n+// not use this file except in compliance with the License.\n+// You may obtain a copy of the License at\n+//\n+//     http://www.apache.org/licenses/LICENSE-2.0\n+//\n+// Unless required by applicable law or agreed to in writing,\n+// software distributed under the License is distributed on an\n+// \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\n+// KIND, either express or implied.  See the License for the\n+// specific language governing permissions and limitations\n+// under the License.\n+\n+package skima\n+\n+import (\n+\t\"strings\"\n+\n+\t\"testing\"\n+\n+\t\"github.com/stretchr/testify/assert\"\n+\n+\t\"fmt\"\n+\n+\t\"sort\"\n+\n+\t\"github.com/elastic/beats/libbeat/common\"\n+)\n+\n+// Is creates a named IsDef with the given checker.\n+func Is(name string, checker Checker) IsDef {\n+\treturn IsDef{name: name, checker: checker}\n+}\n+\n+// Optional wraps an IsDef to mark the field's presence as optional.\n+func Optional(id IsDef) IsDef {\n+\tid.name = \"optional \" + id.name\n+\tid.optional = true\n+\treturn id\n+}\n+\n+// Map is the type used to define schema definitions for Schema.\n+type Map map[string]interface{}\n+\n+// SchemaValidator is the result of Schema and is run against the map you'd like to test.\n+type SchemaValidator func(common.MapStr) SchemaValidationResults\n+\n+// Compose combines multiple SchemaValidators into a single one.\n+func Compose(validators ...SchemaValidator) SchemaValidator {\n+\treturn func(actual common.MapStr) SchemaValidationResults {\n+\t\tresults := make([]SchemaValidationResults, len(validators))\n+\t\tfor idx, validator := range validators {\n+\t\t\tresults[idx] = validator(actual)\n+\t\t}\n+\n+\t\tcombined := SchemaValidationResults{}\n+\t\tfor _, r := range results {\n+\t\t\tr.eachResult(func(path string, vr ValueResult) {\n+\t\t\t\tcombined.recordResult(path, vr)\n+\t\t\t})\n+\t\t}\n+\t\treturn combined\n+\t}\n+}\n+\n+// Strict is used when you want any unspecified keys that are encountered to be considered errors.\n+func Strict(laxValidator SchemaValidator) SchemaValidator {\n+\treturn func(actual common.MapStr) SchemaValidationResults {\n+\t\tvalidatedResults := laxValidator(actual)\n+\n+\t\t// The inner workings of this are a little weird\n+\t\t// We use a hash of dotted paths to track the results\n+\t\t// We can check if a key had a test associated with it by looking up the laxValidator\n+\t\t// result data\n+\t\t// What's trickier is intermediate maps, maps don't usually have explicit tests, they usually just have\n+\t\t// their properties tested.\n+\t\t// This method counts an intermediate map as tested if a subkey is tested.\n+\t\t// Since the datastructure we have to search is a flattened hashmap of the original map we take that hashmap\n+\t\t// and turn it into a sorted string array, then do a binary prefix search to determine if a subkey was tested.\n+\t\t// It's a little weird, but is fairly efficient. We could stop using the flattened map as a datastructure, but\n+\t\t// that would add complexity elsewhere. Probably a good refactor at some point, but not worth it now.\n+\t\tvalidatedPaths := sort.StringSlice{}","path":"heartbeat/skima/core.go","position":86,"original_position":86,"commit_id":"0881a9186c8f13d0c8c6bcb0d89992a60832e0dd","original_commit_id":"0881a9186c8f13d0c8c6bcb0d89992a60832e0dd","user":{"login":"urso","id":75886,"node_id":"MDQ6VXNlcjc1ODg2","avatar_url":"https://avatars1.githubusercontent.com/u/75886?v=4","gravatar_id":"","url":"https://api.github.com/users/urso","html_url":"https://github.com/urso","followers_url":"https://api.github.com/users/urso/followers","following_url":"https://api.github.com/users/urso/following{/other_user}","gists_url":"https://api.github.com/users/urso/gists{/gist_id}","starred_url":"https://api.github.com/users/urso/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/urso/subscriptions","organizations_url":"https://api.github.com/users/urso/orgs","repos_url":"https://api.github.com/users/urso/repos","events_url":"https://api.github.com/users/urso/events{/privacy}","received_events_url":"https://api.github.com/users/urso/received_events","type":"User","site_admin":false},"body":"while it works a more idiomatic use of sort package is to use it only for sorting, not for it's types:\r\n\r\ne.g.\r\n```\r\nvalidatedPaths := make([]string, len(validatedResults))\r\nfor k := range validatedResults {\r\n```","created_at":"2018-07-13T12:22:37Z","updated_at":"2018-07-13T12:22:37Z","html_url":"https://github.com/elastic/beats/pull/7587#discussion_r202332099","pull_request_url":"https://api.github.com/repos/elastic/beats/pulls/7587","author_association":"COLLABORATOR","_links":{"self":{"href":"https://api.github.com/repos/elastic/beats/pulls/comments/202332099"},"html":{"href":"https://github.com/elastic/beats/pull/7587#discussion_r202332099"},"pull_request":{"href":"https://api.github.com/repos/elastic/beats/pulls/7587"}}},"pull_request":{"url":"https://api.github.com/repos/elastic/beats/pulls/7587","id":201139364,"node_id":"MDExOlB1bGxSZXF1ZXN0MjAxMTM5MzY0","html_url":"https://github.com/elastic/beats/pull/7587","diff_url":"https://github.com/elastic/beats/pull/7587.diff","patch_url":"https://github.com/elastic/beats/pull/7587.patch","issue_url":"https://api.github.com/repos/elastic/beats/issues/7587","number":7587,"state":"open","locked":false,"title":"[WIP] Basic Heartbeat Job validation","user":{"login":"andrewvc","id":131427,"node_id":"MDQ6VXNlcjEzMTQyNw==","avatar_url":"https://avatars2.githubusercontent.com/u/131427?v=4","gravatar_id":"","url":"https://api.github.com/users/andrewvc","html_url":"https://github.com/andrewvc","followers_url":"https://api.github.com/users/andrewvc/followers","following_url":"https://api.github.com/users/andrewvc/following{/other_user}","gists_url":"https://api.github.com/users/andrewvc/gists{/gist_id}","starred_url":"https://api.github.com/users/andrewvc/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/andrewvc/subscriptions","organizations_url":"https://api.github.com/users/andrewvc/orgs","repos_url":"https://api.github.com/users/andrewvc/repos","events_url":"https://api.github.com/users/andrewvc/events{/privacy}","received_events_url":"https://api.github.com/users/andrewvc/received_events","type":"User","site_admin":false},"body":"Takes over for #7551 which was getting quite long.\r\n\r\n**Warning, I haven't gone over this line-by-line completely yet. It could probably use some more cleanup. Feedback on the skima DSL would be the most welcome sort of feedback at this point, as well as the general testing approach**\r\n\r\nThis PR seeks to establish a pattern for testing heartbeat jobs. It currently tests the HTTP and TCP jobs.\r\n\r\nTo do this, it made sense to validate event maps with a sort of schema library. I couldn't find one that did exactly what I wanted here, so I wrote one called `skima`.\r\n\r\nOne of the nicest things about the heartbeat architecture is the dialer chain behavior. It should be the case that any validated protocol using TCP (e.g. HTTP, TCP, Redis, etc.) has the *exact* same tcp metadata. \r\n\r\nTo help make testing these properties easy `skima` lets users compose portions of a schema into a bigger one. In other words, you can say \"An HTTP response should be a TCP response, with the standard monitor data added in, and also the special HTTP fields\". Even having only written a handful of tests this has uncovered some inconsistencies there, where TCP jobs have a hostname, but HTTP ones do not. It's also uncovered that HTTP 5xx responses don't include a status code, while 2xx ones do.\r\n\r\nFiles worth looking at first:\r\n\r\n1. HTTP Test: ([monitors/active/http/http_test.go](https://github.com/andrewvc/beats/blob/job_validation/heartbeat/monitors/active/http/http_test.go#L83))\r\n2. TCP Test: ([monitors/active/tcp/tcp_test.go](https://github.com/andrewvc/beats/blob/job_validation/heartbeat/monitors/active/tcp/tcp_test.go#L31)\r\n3. Skima Unit Tests: ([skima/core_test.go](https://github.com/andrewvc/beats/blob/job_validation/heartbeat/skima/core_test.go))\r\n\r\n**Adendum, why I didn't choose other schema libs**\r\n\r\nWell, this started off as being a very simple set of functions, and here we are. Still, that's not a good reason to write something new. So, let's compare:\r\n\r\n1. [go-playground/go-validator](https://godoc.org/gopkg.in/go-playground/validator.v9) : awkward tag based syntax. A goal here was clarity, this isn't so easy to use.\r\n2. [ozzo-validation](https://github.com/go-ozzo/ozzo-validation). Seems mostly targeted at web apps / forms. Doesn't really have a validation def that 'looks' like the end representation, which is a key goal of 'skima'\r\n3. [go-validator/validator](https://github.com/go-validator/validator) once again, awkward tag based syntax.","created_at":"2018-07-12T22:09:28Z","updated_at":"2018-07-13T12:22:37Z","closed_at":null,"merged_at":null,"merge_commit_sha":"d3a7a55fb4e0d6523d0b10d92272eed3c9c3034f","assignee":null,"assignees":[],"requested_reviewers":[],"requested_teams":[],"labels":[{"id":459123149,"node_id":"MDU6TGFiZWw0NTkxMjMxNDk=","url":"https://api.github.com/repos/elastic/beats/labels/Heartbeat","name":"Heartbeat","color":"008000","default":false},{"id":216798909,"node_id":"MDU6TGFiZWwyMTY3OTg5MDk=","url":"https://api.github.com/repos/elastic/beats/labels/in%20progress","name":"in progress","color":"207de5","default":false},{"id":360512612,"node_id":"MDU6TGFiZWwzNjA1MTI2MTI=","url":"https://api.github.com/repos/elastic/beats/labels/needs%20tests","name":"needs tests","color":"cc317c","default":false}],"milestone":null,"commits_url":"https://api.github.com/repos/elastic/beats/pulls/7587/commits","review_comments_url":"https://api.github.com/repos/elastic/beats/pulls/7587/comments","review_comment_url":"https://api.github.com/repos/elastic/beats/pulls/comments{/number}","comments_url":"https://api.github.com/repos/elastic/beats/issues/7587/comments","statuses_url":"https://api.github.com/repos/elastic/beats/statuses/0881a9186c8f13d0c8c6bcb0d89992a60832e0dd","head":{"label":"andrewvc:job_validation","ref":"job_validation","sha":"0881a9186c8f13d0c8c6bcb0d89992a60832e0dd","user":{"login":"andrewvc","id":131427,"node_id":"MDQ6VXNlcjEzMTQyNw==","avatar_url":"https://avatars2.githubusercontent.com/u/131427?v=4","gravatar_id":"","url":"https://api.github.com/users/andrewvc","html_url":"https://github.com/andrewvc","followers_url":"https://api.github.com/users/andrewvc/followers","following_url":"https://api.github.com/users/andrewvc/following{/other_user}","gists_url":"https://api.github.com/users/andrewvc/gists{/gist_id}","starred_url":"https://api.github.com/users/andrewvc/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/andrewvc/subscriptions","organizations_url":"https://api.github.com/users/andrewvc/orgs","repos_url":"https://api.github.com/users/andrewvc/repos","events_url":"https://api.github.com/users/andrewvc/events{/privacy}","received_events_url":"https://api.github.com/users/andrewvc/received_events","type":"User","site_admin":false},"repo":{"id":119769836,"node_id":"MDEwOlJlcG9zaXRvcnkxMTk3Njk4MzY=","name":"beats","full_name":"andrewvc/beats","owner":{"login":"andrewvc","id":131427,"node_id":"MDQ6VXNlcjEzMTQyNw==","avatar_url":"https://avatars2.githubusercontent.com/u/131427?v=4","gravatar_id":"","url":"https://api.github.com/users/andrewvc","html_url":"https://github.com/andrewvc","followers_url":"https://api.github.com/users/andrewvc/followers","following_url":"https://api.github.com/users/andrewvc/following{/other_user}","gists_url":"https://api.github.com/users/andrewvc/gists{/gist_id}","starred_url":"https://api.github.com/users/andrewvc/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/andrewvc/subscriptions","organizations_url":"https://api.github.com/users/andrewvc/orgs","repos_url":"https://api.github.com/users/andrewvc/repos","events_url":"https://api.github.com/users/andrewvc/events{/privacy}","received_events_url":"https://api.github.com/users/andrewvc/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/andrewvc/beats","description":":tropical_fish: Beats - Lightweight shippers for Elasticsearch & Logstash ","fork":true,"url":"https://api.github.com/repos/andrewvc/beats","forks_url":"https://api.github.com/repos/andrewvc/beats/forks","keys_url":"https://api.github.com/repos/andrewvc/beats/keys{/key_id}","collaborators_url":"https://api.github.com/repos/andrewvc/beats/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/andrewvc/beats/teams","hooks_url":"https://api.github.com/repos/andrewvc/beats/hooks","issue_events_url":"https://api.github.com/repos/andrewvc/beats/issues/events{/number}","events_url":"https://api.github.com/repos/andrewvc/beats/events","assignees_url":"https://api.github.com/repos/andrewvc/beats/assignees{/user}","branches_url":"https://api.github.com/repos/andrewvc/beats/branches{/branch}","tags_url":"https://api.github.com/repos/andrewvc/beats/tags","blobs_url":"https://api.github.com/repos/andrewvc/beats/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/andrewvc/beats/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/andrewvc/beats/git/refs{/sha}","trees_url":"https://api.github.com/repos/andrewvc/beats/git/trees{/sha}","statuses_url":"https://api.github.com/repos/andrewvc/beats/statuses/{sha}","languages_url":"https://api.github.com/repos/andrewvc/beats/languages","stargazers_url":"https://api.github.com/repos/andrewvc/beats/stargazers","contributors_url":"https://api.github.com/repos/andrewvc/beats/contributors","subscribers_url":"https://api.github.com/repos/andrewvc/beats/subscribers","subscription_url":"https://api.github.com/repos/andrewvc/beats/subscription","commits_url":"https://api.github.com/repos/andrewvc/beats/commits{/sha}","git_commits_url":"https://api.github.com/repos/andrewvc/beats/git/commits{/sha}","comments_url":"https://api.github.com/repos/andrewvc/beats/comments{/number}","issue_comment_url":"https://api.github.com/repos/andrewvc/beats/issues/comments{/number}","contents_url":"https://api.github.com/repos/andrewvc/beats/contents/{+path}","compare_url":"https://api.github.com/repos/andrewvc/beats/compare/{base}...{head}","merges_url":"https://api.github.com/repos/andrewvc/beats/merges","archive_url":"https://api.github.com/repos/andrewvc/beats/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/andrewvc/beats/downloads","issues_url":"https://api.github.com/repos/andrewvc/beats/issues{/number}","pulls_url":"https://api.github.com/repos/andrewvc/beats/pulls{/number}","milestones_url":"https://api.github.com/repos/andrewvc/beats/milestones{/number}","notifications_url":"https://api.github.com/repos/andrewvc/beats/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/andrewvc/beats/labels{/name}","releases_url":"https://api.github.com/repos/andrewvc/beats/releases{/id}","deployments_url":"https://api.github.com/repos/andrewvc/beats/deployments","created_at":"2018-02-01T02:11:47Z","updated_at":"2018-02-01T02:11:55Z","pushed_at":"2018-07-12T22:02:37Z","git_url":"git://github.com/andrewvc/beats.git","ssh_url":"git@github.com:andrewvc/beats.git","clone_url":"https://github.com/andrewvc/beats.git","svn_url":"https://github.com/andrewvc/beats","homepage":"https://www.elastic.co/products/beats","size":83390,"stargazers_count":0,"watchers_count":0,"language":"Go","has_issues":false,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"archived":false,"open_issues_count":0,"license":{"key":"other","name":"Other","spdx_id":null,"url":null,"node_id":"MDc6TGljZW5zZTA="},"forks":0,"open_issues":0,"watchers":0,"default_branch":"master"}},"base":{"label":"elastic:master","ref":"master","sha":"9e768f3489cc831e0231b8dbafae8fc439b58c62","user":{"login":"elastic","id":6764390,"node_id":"MDEyOk9yZ2FuaXphdGlvbjY3NjQzOTA=","avatar_url":"https://avatars0.githubusercontent.com/u/6764390?v=4","gravatar_id":"","url":"https://api.github.com/users/elastic","html_url":"https://github.com/elastic","followers_url":"https://api.github.com/users/elastic/followers","following_url":"https://api.github.com/users/elastic/following{/other_user}","gists_url":"https://api.github.com/users/elastic/gists{/gist_id}","starred_url":"https://api.github.com/users/elastic/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/elastic/subscriptions","organizations_url":"https://api.github.com/users/elastic/orgs","repos_url":"https://api.github.com/users/elastic/repos","events_url":"https://api.github.com/users/elastic/events{/privacy}","received_events_url":"https://api.github.com/users/elastic/received_events","type":"Organization","site_admin":false},"repo":{"id":16554739,"node_id":"MDEwOlJlcG9zaXRvcnkxNjU1NDczOQ==","name":"beats","full_name":"elastic/beats","owner":{"login":"elastic","id":6764390,"node_id":"MDEyOk9yZ2FuaXphdGlvbjY3NjQzOTA=","avatar_url":"https://avatars0.githubusercontent.com/u/6764390?v=4","gravatar_id":"","url":"https://api.github.com/users/elastic","html_url":"https://github.com/elastic","followers_url":"https://api.github.com/users/elastic/followers","following_url":"https://api.github.com/users/elastic/following{/other_user}","gists_url":"https://api.github.com/users/elastic/gists{/gist_id}","starred_url":"https://api.github.com/users/elastic/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/elastic/subscriptions","organizations_url":"https://api.github.com/users/elastic/orgs","repos_url":"https://api.github.com/users/elastic/repos","events_url":"https://api.github.com/users/elastic/events{/privacy}","received_events_url":"https://api.github.com/users/elastic/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/elastic/beats","description":":tropical_fish: Beats - Lightweight shippers for Elasticsearch & Logstash ","fork":false,"url":"https://api.github.com/repos/elastic/beats","forks_url":"https://api.github.com/repos/elastic/beats/forks","keys_url":"https://api.github.com/repos/elastic/beats/keys{/key_id}","collaborators_url":"https://api.github.com/repos/elastic/beats/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/elastic/beats/teams","hooks_url":"https://api.github.com/repos/elastic/beats/hooks","issue_events_url":"https://api.github.com/repos/elastic/beats/issues/events{/number}","events_url":"https://api.github.com/repos/elastic/beats/events","assignees_url":"https://api.github.com/repos/elastic/beats/assignees{/user}","branches_url":"https://api.github.com/repos/elastic/beats/branches{/branch}","tags_url":"https://api.github.com/repos/elastic/beats/tags","blobs_url":"https://api.github.com/repos/elastic/beats/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/elastic/beats/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/elastic/beats/git/refs{/sha}","trees_url":"https://api.github.com/repos/elastic/beats/git/trees{/sha}","statuses_url":"https://api.github.com/repos/elastic/beats/statuses/{sha}","languages_url":"https://api.github.com/repos/elastic/beats/languages","stargazers_url":"https://api.github.com/repos/elastic/beats/stargazers","contributors_url":"https://api.github.com/repos/elastic/beats/contributors","subscribers_url":"https://api.github.com/repos/elastic/beats/subscribers","subscription_url":"https://api.github.com/repos/elastic/beats/subscription","commits_url":"https://api.github.com/repos/elastic/beats/commits{/sha}","git_commits_url":"https://api.github.com/repos/elastic/beats/git/commits{/sha}","comments_url":"https://api.github.com/repos/elastic/beats/comments{/number}","issue_comment_url":"https://api.github.com/repos/elastic/beats/issues/comments{/number}","contents_url":"https://api.github.com/repos/elastic/beats/contents/{+path}","compare_url":"https://api.github.com/repos/elastic/beats/compare/{base}...{head}","merges_url":"https://api.github.com/repos/elastic/beats/merges","archive_url":"https://api.github.com/repos/elastic/beats/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/elastic/beats/downloads","issues_url":"https://api.github.com/repos/elastic/beats/issues{/number}","pulls_url":"https://api.github.com/repos/elastic/beats/pulls{/number}","milestones_url":"https://api.github.com/repos/elastic/beats/milestones{/number}","notifications_url":"https://api.github.com/repos/elastic/beats/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/elastic/beats/labels{/name}","releases_url":"https://api.github.com/repos/elastic/beats/releases{/id}","deployments_url":"https://api.github.com/repos/elastic/beats/deployments","created_at":"2014-02-05T19:01:02Z","updated_at":"2018-07-13T12:02:14Z","pushed_at":"2018-07-13T12:21:39Z","git_url":"git://github.com/elastic/beats.git","ssh_url":"git@github.com:elastic/beats.git","clone_url":"https://github.com/elastic/beats.git","svn_url":"https://github.com/elastic/beats","homepage":"https://www.elastic.co/products/beats","size":84223,"stargazers_count":5810,"watchers_count":5810,"language":"Go","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":1777,"mirror_url":null,"archived":false,"open_issues_count":826,"license":{"key":"other","name":"Other","spdx_id":null,"url":null,"node_id":"MDc6TGljZW5zZTA="},"forks":1777,"open_issues":826,"watchers":5810,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/elastic/beats/pulls/7587"},"html":{"href":"https://github.com/elastic/beats/pull/7587"},"issue":{"href":"https://api.github.com/repos/elastic/beats/issues/7587"},"comments":{"href":"https://api.github.com/repos/elastic/beats/issues/7587/comments"},"review_comments":{"href":"https://api.github.com/repos/elastic/beats/pulls/7587/comments"},"review_comment":{"href":"https://api.github.com/repos/elastic/beats/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/elastic/beats/pulls/7587/commits"},"statuses":{"href":"https://api.github.com/repos/elastic/beats/statuses/0881a9186c8f13d0c8c6bcb0d89992a60832e0dd"}},"author_association":"CONTRIBUTOR"}}
{"actor":{"display_login":"urso"}}