blob: 2afa88cdf2dd59ea15018b246c0a59ecdd015296 [file] [log] [blame]
;; Licensed to the Apache Software Foundation (ASF) under one
;; or more contributor license agreements. See the NOTICE file
;; distributed with this work for additional information
;; regarding copyright ownership. The ASF licenses this file
;; to you under the Apache License, Version 2.0 (the
;; "License"); you may not use this file except in compliance
;; with the License. You may obtain a copy of the License at
;;
;; http://www.apache.org/licenses/LICENSE-2.0
;;
;; Unless required by applicable law or agreed to in writing, software
;; distributed under the License is distributed on an "AS IS" BASIS,
;; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
;; See the License for the specific language governing permissions and
;; limitations under the License.
(ns backtype.storm.logviewer-test
(:use [backtype.storm config util])
(:require [backtype.storm.daemon [logviewer :as logviewer]
[supervisor :as supervisor]])
(:require [conjure.core])
(:use [clojure test])
(:use [conjure core])
(:use [backtype.storm.ui helpers])
(:import [java.nio.file Files])
(:import [java.nio.file.attribute FileAttribute])
(:import [java.io File])
(:import [org.mockito Mockito]))
(defmulti mk-mock-File #(:type %))
(defmethod mk-mock-File :file [{file-name :name mtime :mtime length :length
:or {file-name "afile"
mtime 1
length (* 10 (* 1024 (* 1024 1024))) }}] ; Length 10 GB
(let [mockFile (Mockito/mock java.io.File)]
(. (Mockito/when (.getName mockFile)) thenReturn file-name)
(. (Mockito/when (.lastModified mockFile)) thenReturn mtime)
(. (Mockito/when (.isFile mockFile)) thenReturn true)
(. (Mockito/when (.getCanonicalPath mockFile))
thenReturn (str "/mock/canonical/path/to/" file-name))
(. (Mockito/when (.length mockFile)) thenReturn length)
mockFile))
(defmethod mk-mock-File :directory [{dir-name :name mtime :mtime files :files
:or {dir-name "adir" mtime 1 files []}}]
(let [mockDir (Mockito/mock java.io.File)]
(. (Mockito/when (.getName mockDir)) thenReturn dir-name)
(. (Mockito/when (.lastModified mockDir)) thenReturn mtime)
(. (Mockito/when (.isFile mockDir)) thenReturn false)
(. (Mockito/when (.listFiles mockDir)) thenReturn (into-array File files))
mockDir))
(deftest test-mk-FileFilter-for-log-cleanup
(testing "log file filter selects the correct worker-log dirs for purge"
(let [now-millis (current-time-millis)
conf {LOGVIEWER-CLEANUP-AGE-MINS 60
LOGVIEWER-CLEANUP-INTERVAL-SECS 300}
cutoff-millis (logviewer/cleanup-cutoff-age-millis conf now-millis)
old-mtime-millis (- cutoff-millis 500)
new-mtime-millis (+ cutoff-millis 500)
matching-files (map #(mk-mock-File %)
[{:name "3031"
:type :directory
:mtime old-mtime-millis}
{:name "3032"
:type :directory
:mtime old-mtime-millis}
{:name "7077"
:type :directory
:mtime old-mtime-millis}])
excluded-files (map #(mk-mock-File %)
[{:name "oldlog-1-2-worker-.log"
:type :file
:mtime old-mtime-millis}
{:name "newlog-1-2-worker.log"
:type :file
:mtime new-mtime-millis}
{:name "some-old-file.txt"
:type :file
:mtime old-mtime-millis}
{:name "olddir-1-2-worker.log"
:type :directory
:mtime new-mtime-millis}
{:name "metadata"
:type :directory
:mtime new-mtime-millis}
{:name "newdir"
:type :directory
:mtime new-mtime-millis}
])
file-filter (logviewer/mk-FileFilter-for-log-cleanup conf now-millis)]
(is (every? #(.accept file-filter %) matching-files))
(is (not-any? #(.accept file-filter %) excluded-files))
)))
(deftest test-sort-worker-logs
(testing "cleaner sorts the log files in ascending ages for deletion"
(stubbing [logviewer/filter-candidate-files (fn [x _] x)]
(let [now-millis (current-time-millis)
files1 (into-array File (map #(mk-mock-File {:name (str %)
:type :file
:mtime (- now-millis (* 100 %))})
(range 1 6)))
files2 (into-array File (map #(mk-mock-File {:name (str %)
:type :file
:mtime (- now-millis (* 100 %))})
(range 6 11)))
files3 (into-array File (map #(mk-mock-File {:name (str %)
:type :file
:mtime (- now-millis (* 100 %))})
(range 11 16)))
port1-dir (mk-mock-File {:name "/workers-artifacts/topo1/port1"
:type :directory
:files files1})
port2-dir (mk-mock-File {:name "/workers-artifacts/topo1/port2"
:type :directory
:files files2})
port3-dir (mk-mock-File {:name "/workers-artifacts/topo2/port3"
:type :directory
:files files3})
topo1-files (into-array File [port1-dir port2-dir])
topo2-files (into-array File [port3-dir])
topo1-dir (mk-mock-File {:name "/workers-artifacts/topo1"
:type :directory
:files topo1-files})
topo2-dir (mk-mock-File {:name "/workers-artifacts/topo2"
:type :directory
:files topo2-files})
root-files (into-array File [topo1-dir topo2-dir])
root-dir (mk-mock-File {:name "/workers-artifacts"
:type :directory
:files root-files})
sorted-logs (logviewer/sorted-worker-logs root-dir)
sorted-ints (map #(Integer. (.getName %)) sorted-logs)]
(is (= (count sorted-logs) 15))
(is (= (count sorted-ints) 15))
(is (apply #'> sorted-ints))))))
(deftest test-per-workerdir-cleanup
(testing "cleaner deletes oldest files in each worker dir if files are larger than per-dir quota."
(stubbing [rmr nil]
(let [now-millis (current-time-millis)
files1 (into-array File (map #(mk-mock-File {:name (str "A" %)
:type :file
:mtime (+ now-millis (* 100 %))
:length 200 })
(range 0 10)))
files2 (into-array File (map #(mk-mock-File {:name (str "B" %)
:type :file
:mtime (+ now-millis (* 100 %))
:length 200 })
(range 0 10)))
files3 (into-array File (map #(mk-mock-File {:name (str "C" %)
:type :file
:mtime (+ now-millis (* 100 %))
:length 200 })
(range 0 10)))
port1-dir (mk-mock-File {:name "/workers-artifacts/topo1/port1"
:type :directory
:files files1})
port2-dir (mk-mock-File {:name "/workers-artifacts/topo1/port2"
:type :directory
:files files2})
port3-dir (mk-mock-File {:name "/workers-artifacts/topo2/port3"
:type :directory
:files files3})
topo1-files (into-array File [port1-dir port2-dir])
topo2-files (into-array File [port3-dir])
topo1-dir (mk-mock-File {:name "/workers-artifacts/topo1"
:type :directory
:files topo1-files})
topo2-dir (mk-mock-File {:name "/workers-artifacts/topo2"
:type :directory
:files topo2-files})
root-files (into-array File [topo1-dir topo2-dir])
root-dir (mk-mock-File {:name "/workers-artifacts"
:type :directory
:files root-files})
remaining-logs (logviewer/per-workerdir-cleanup root-dir 1200)]
(is (= (count (first remaining-logs)) 6))
(is (= (count (second remaining-logs)) 6))
(is (= (count (last remaining-logs)) 6))))))
(deftest test-delete-oldest-log-cleanup
(testing "delete oldest logs deletes the oldest set of logs when the total size gets too large."
(stubbing [rmr nil]
(let [now-millis (current-time-millis)
files (into-array File (map #(mk-mock-File {:name (str %)
:type :file
:mtime (+ now-millis (* 100 %))
:length 100 })
(range 0 20)))
remaining-logs (logviewer/delete-oldest-while-logs-too-large files 501)]
(is (= (logviewer/sum-file-size files) 2000))
(is (= (count remaining-logs) 5))
(is (= (.getName (first remaining-logs)) "15"))))))
(deftest test-identify-worker-log-dirs
(testing "Build up workerid-workerlogdir map for the old workers' dirs"
(let [port1-dir (mk-mock-File {:name "/workers-artifacts/topo1/port1"
:type :directory})
mock-metaFile (mk-mock-File {:name "worker.yaml"
:type :file})
exp-id "id12345"
expected {exp-id port1-dir}]
(stubbing [supervisor/read-worker-heartbeats nil
logviewer/get-metadata-file-for-wroker-logdir mock-metaFile
logviewer/get-worker-id-from-metadata-file exp-id]
(is (= expected (logviewer/identify-worker-log-dirs [port1-dir])))))))
(deftest test-get-dead-worker-dirs
(testing "removes any files of workers that are still alive"
(let [conf {SUPERVISOR-WORKER-TIMEOUT-SECS 5}
id->hb {"42" {:time-secs 1}}
now-secs 2
unexpected-dir (mk-mock-File {:name "dir1" :type :directory})
expected-dir (mk-mock-File {:name "dir2" :type :directory})
log-dirs #{unexpected-dir expected-dir}]
(stubbing [logviewer/identify-worker-log-dirs {"42" unexpected-dir,
"007" expected-dir}
supervisor/read-worker-heartbeats id->hb]
(is (= #{expected-dir}
(logviewer/get-dead-worker-dirs conf now-secs log-dirs)))))))
(deftest test-cleanup-fn
(testing "cleanup function rmr's files of dead workers"
(let [mockfile1 (mk-mock-File {:name "delete-me1" :type :file})
mockfile2 (mk-mock-File {:name "delete-me2" :type :file})]
(stubbing [logviewer/select-dirs-for-cleanup nil
logviewer/get-dead-worker-dirs (sorted-set mockfile1 mockfile2)
logviewer/cleanup-empty-topodir nil
rmr nil]
(logviewer/cleanup-fn! "/bogus/path")
(verify-call-times-for rmr 2)
(verify-nth-call-args-for 1 rmr (.getCanonicalPath mockfile1))
(verify-nth-call-args-for 2 rmr (.getCanonicalPath mockfile2))))))
(deftest test-authorized-log-user
(testing "allow cluster admin"
(let [conf {NIMBUS-ADMINS ["alice"]}]
(stubbing [logviewer/get-log-user-group-whitelist [[] []]
logviewer/user-groups []]
(is (logviewer/authorized-log-user? "alice" "non-blank-fname" conf))
(verify-first-call-args-for logviewer/get-log-user-group-whitelist "non-blank-fname")
(verify-first-call-args-for logviewer/user-groups "alice"))))
(testing "ignore any cluster-set topology.users topology.groups"
(let [conf {TOPOLOGY-USERS ["alice"]
TOPOLOGY-GROUPS ["alice-group"]}]
(stubbing [logviewer/get-log-user-group-whitelist [[] []]
logviewer/user-groups ["alice-group"]]
(is (not (logviewer/authorized-log-user? "alice" "non-blank-fname" conf)))
(verify-first-call-args-for logviewer/get-log-user-group-whitelist "non-blank-fname")
(verify-first-call-args-for logviewer/user-groups "alice"))))
(testing "allow cluster logs user"
(let [conf {LOGS-USERS ["alice"]}]
(stubbing [logviewer/get-log-user-group-whitelist [[] []]
logviewer/user-groups []]
(is (logviewer/authorized-log-user? "alice" "non-blank-fname" conf))
(verify-first-call-args-for logviewer/get-log-user-group-whitelist "non-blank-fname")
(verify-first-call-args-for logviewer/user-groups "alice"))))
(testing "allow whitelisted topology user"
(stubbing [logviewer/get-log-user-group-whitelist [["alice"] []]
logviewer/user-groups []]
(is (logviewer/authorized-log-user? "alice" "non-blank-fname" {}))
(verify-first-call-args-for logviewer/get-log-user-group-whitelist "non-blank-fname")
(verify-first-call-args-for logviewer/user-groups "alice")))
(testing "allow whitelisted topology group"
(stubbing [logviewer/get-log-user-group-whitelist [[] ["alice-group"]]
logviewer/user-groups ["alice-group"]]
(is (logviewer/authorized-log-user? "alice" "non-blank-fname" {}))
(verify-first-call-args-for logviewer/get-log-user-group-whitelist "non-blank-fname")
(verify-first-call-args-for logviewer/user-groups "alice")))
(testing "disallow user not in nimbus admin, topo user, logs user, or whitelist"
(stubbing [logviewer/get-log-user-group-whitelist [[] []]
logviewer/user-groups []]
(is (not (logviewer/authorized-log-user? "alice" "non-blank-fname" {})))
(verify-first-call-args-for logviewer/get-log-user-group-whitelist "non-blank-fname")
(verify-first-call-args-for logviewer/user-groups "alice"))))
(deftest test-list-log-files
(testing "list-log-files filter selects the correct log files to return"
(let [attrs (make-array FileAttribute 0)
root-path (.getCanonicalPath (.toFile (Files/createTempDirectory "workers-artifacts" attrs)))
file1 (clojure.java.io/file root-path "topoA" "port1" "worker.log")
file2 (clojure.java.io/file root-path "topoA" "port2" "worker.log")
file3 (clojure.java.io/file root-path "topoB" "port1" "worker.log")
_ (clojure.java.io/make-parents file1)
_ (clojure.java.io/make-parents file2)
_ (clojure.java.io/make-parents file3)
_ (.createNewFile file1)
_ (.createNewFile file2)
_ (.createNewFile file3)
origin "www.origin.server.net"
expected-all (json-response '("topoA/port1/worker.log" "topoA/port2/worker.log"
"topoB/port1/worker.log")
nil
:headers {"Access-Control-Allow-Origin" origin
"Access-Control-Allow-Credentials" "true"})
expected-filter-port (json-response '("topoA/port1/worker.log" "topoB/port1/worker.log")
nil
:headers {"Access-Control-Allow-Origin" origin
"Access-Control-Allow-Credentials" "true"})
expected-filter-topoId (json-response '("topoB/port1/worker.log")
nil
:headers {"Access-Control-Allow-Origin" origin
"Access-Control-Allow-Credentials" "true"})
returned-all (logviewer/list-log-files "user" nil nil root-path nil origin)
returned-filter-port (logviewer/list-log-files "user" nil "port1" root-path nil origin)
returned-filter-topoId (logviewer/list-log-files "user" "topoB" nil root-path nil origin)]
(rmr root-path)
(is (= expected-all returned-all))
(is (= expected-filter-port returned-filter-port))
(is (= expected-filter-topoId returned-filter-topoId)))))
(deftest test-search-via-rest-api
(testing "Throws if bogus file is given"
(thrown-cause? java.lang.RuntimeException
(logviewer/substring-search nil "a string")))
(let [pattern "needle"
expected-host "dev.null.invalid"
expected-port 8888
;; When we click a link to the logviewer, we expect the match line to
;; be somewhere near the middle of the page. So we subtract half of
;; the default page length from the offset at which we found the
;; match.
exp-offset-fn #(- (/ logviewer/default-bytes-per-page 2) %)]
(stubbing [local-hostname expected-host
logviewer/logviewer-port expected-port]
(testing "Logviewer link centers the match in the page"
(let [expected-fname "foobar.log"]
(is (= (str "http://"
expected-host
":"
expected-port
"/log?file="
expected-fname
"&start=1947&length="
logviewer/default-bytes-per-page)
(logviewer/url-to-match-centered-in-log-page (byte-array 42)
expected-fname
27526
8888)))))
(let [file (->> "logviewer-search-context-tests.log"
(clojure.java.io/file "src" "dev"))]
(testing "returns correct before/after context"
(is (= {"searchString" pattern
"startByteOffset" 0
"matches" [{"byteOffset" 0
"beforeString" ""
"afterString" " needle000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000needle "
"matchString" pattern
"logviewerURL" (str "http://"
expected-host
":"
expected-port
"/log?file=src%2Fdev%2F"
(.getName file)
"&start=0&length=51200")}
{"byteOffset" 7
"beforeString" "needle "
"afterString" "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000needle needle\n"
"matchString" pattern
"logviewerURL" (str "http://"
expected-host
":"
expected-port
"/log?file=src%2Fdev%2F"
(.getName file)
"&start=0&length=51200")}
{"byteOffset" 127
"beforeString" "needle needle000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
"afterString" " needle\n"
"matchString" pattern
"logviewerURL" (str "http://"
expected-host
":"
expected-port
"/log?file=src%2Fdev%2F"
(.getName file)
"&start=0&length=51200")}
{"byteOffset" 134
"beforeString" " needle000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000needle "
"afterString" "\n"
"matchString" pattern
"logviewerURL" (str "http://"
expected-host
":"
expected-port
"/log?file=src%2Fdev%2F"
(.getName file)
"&start=0&length=51200")}
]}
(logviewer/substring-search file pattern)))))
(let [file (clojure.java.io/file "src" "dev" "small-worker.log")]
(testing "a really small log file"
(is (= {"searchString" pattern
"startByteOffset" 0
"matches" [{"byteOffset" 7
"beforeString" "000000 "
"afterString" " 000000\n"
"matchString" pattern
"logviewerURL" (str "http://"
expected-host
":"
expected-port
"/log?file=src%2Fdev%2F"
(.getName file)
"&start=0&length=51200")}]}
(logviewer/substring-search file pattern)))))
(let [file (clojure.java.io/file "src" "dev" "test-3072.log")]
(testing "no offset returned when file ends on buffer offset"
(let [expected
{"searchString" pattern
"startByteOffset" 0
"matches" [{"byteOffset" 3066
"beforeString" (->>
(repeat 128 '.)
clojure.string/join)
"afterString" ""
"matchString" pattern
"logviewerURL" (str "http://"
expected-host
":"
expected-port
"/log?file=src%2Fdev%2F"
(.getName file)
"&start=0&length=51200")}]}]
(is (= expected
(logviewer/substring-search file pattern)))
(is (= expected
(logviewer/substring-search file pattern :num-matches 1))))))
(let [file (clojure.java.io/file "src" "dev" "test-worker.log")]
(testing "next byte offsets are correct for each match"
(doseq [[num-matches-sought
num-matches-found
expected-next-byte-offset] [[1 1 11]
[2 2 2042]
[3 3 2052]
[4 4 3078]
[5 5 3196]
[6 6 3202]
[7 7 6252]
[8 8 6321]
[9 9 6397]
[10 10 6476]
[11 11 6554]
[12 12 nil]
[13 12 nil]]]
(let [result
(logviewer/substring-search file
pattern
:num-matches num-matches-sought)]
(is (= expected-next-byte-offset
(get result "nextByteOffset")))
(is (= num-matches-found (count (get result "matches")))))))
(is
(= {"nextByteOffset" 6252
"searchString" pattern
"startByteOffset" 0
"matches" [
{"byteOffset" 5
"beforeString" "Test "
"afterString" " is near the beginning of the file.\nThis file assumes a buffer size of 2048 bytes, a max search string size of 1024 bytes, and a"
"matchString" pattern
"logviewerURL" (str "http://"
expected-host
":"
expected-port
"/log?file=src%2Fdev%2F"
(.getName file)
"&start=0&length=51200")}
{"byteOffset" 2036
"beforeString" "ng 146\npadding 147\npadding 148\npadding 149\npadding 150\npadding 151\npadding 152\npadding 153\nNear the end of a 1024 byte block, a "
"afterString" ".\nA needle that straddles a 1024 byte boundary should also be detected.\n\npadding 157\npadding 158\npadding 159\npadding 160\npadding"
"matchString" pattern
"logviewerURL" (str "http://"
expected-host
":"
expected-port
"/log?file=src%2Fdev%2F"
(.getName file)
"&start=0&length=51200")}
{"byteOffset" 2046
"beforeString" "ding 147\npadding 148\npadding 149\npadding 150\npadding 151\npadding 152\npadding 153\nNear the end of a 1024 byte block, a needle.\nA "
"afterString" " that straddles a 1024 byte boundary should also be detected.\n\npadding 157\npadding 158\npadding 159\npadding 160\npadding 161\npaddi"
"matchString" pattern
"logviewerURL" (str "http://"
expected-host
":"
expected-port
"/log?file=src%2Fdev%2F"
(.getName file)
"&start=0&length=51200")}
{"byteOffset" 3072
"beforeString" "adding 226\npadding 227\npadding 228\npadding 229\npadding 230\npadding 231\npadding 232\npadding 233\npadding 234\npadding 235\n\n\nHere a "
"afterString" " occurs just after a 1024 byte boundary. It should have the correct context.\n\nText with two adjoining matches: needleneedle\n\npa"
"matchString" pattern
"logviewerURL" (str "http://"
expected-host
":"
expected-port
"/log?file=src%2Fdev%2F"
(.getName file)
"&start=0&length=51200")}
{"byteOffset" 3190
"beforeString" "\n\n\nHere a needle occurs just after a 1024 byte boundary. It should have the correct context.\n\nText with two adjoining matches: "
"afterString" "needle\n\npadding 243\npadding 244\npadding 245\npadding 246\npadding 247\npadding 248\npadding 249\npadding 250\npadding 251\npadding 252\n"
"matchString" pattern
"logviewerURL" (str "http://"
expected-host
":"
expected-port
"/log?file=src%2Fdev%2F"
(.getName file)
"&start=0&length=51200")}
{"byteOffset" 3196
"beforeString" "e a needle occurs just after a 1024 byte boundary. It should have the correct context.\n\nText with two adjoining matches: needle"
"afterString" "\n\npadding 243\npadding 244\npadding 245\npadding 246\npadding 247\npadding 248\npadding 249\npadding 250\npadding 251\npadding 252\npaddin"
"matchString" pattern
"logviewerURL" (str "http://"
expected-host
":"
expected-port
"/log?file=src%2Fdev%2F"
(.getName file)
"&start=0&length=51200")}
{"byteOffset" 6246
"beforeString" "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX\n\nHere are four non-ascii 1-byte UTF-8 characters: αβγδε\n\n"
"afterString" "\n\nHere are four printable 2-byte UTF-8 characters: ¡¢£¤¥\n\nneedle\n\n\n\nHere are four printable 3-byte UTF-8 characters: ऄअ"
"matchString" pattern
"logviewerURL" (str "http://"
expected-host
":"
expected-port
"/log?file=src%2Fdev%2F"
(.getName file)
"&start=0&length=51200")}
]}
(logviewer/substring-search file pattern :num-matches 7)))
(testing "Correct match offset is returned when skipping bytes"
(let [start-byte-offset 3197]
(is (= {"nextByteOffset" 6252
"searchString" pattern
"startByteOffset" start-byte-offset
"matches" [{"byteOffset" 6246
"beforeString" "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX\n\nHere are four non-ascii 1-byte UTF-8 characters: αβγδε\n\n"
"afterString" "\n\nHere are four printable 2-byte UTF-8 characters: ¡¢£¤¥\n\nneedle\n\n\n\nHere are four printable 3-byte UTF-8 characters: ऄअ"
"matchString" pattern
"logviewerURL" (str "http://"
expected-host
":"
expected-port
"/log?file=src%2Fdev%2F"
(.getName file)
"&start=0&length=51200")}]}
(logviewer/substring-search file
pattern
:num-matches 1
:start-byte-offset start-byte-offset)))))
(let [pattern (clojure.string/join (repeat 1024 'X))]
(is
(= {"nextByteOffset" 6183
"searchString" pattern
"startByteOffset" 0
"matches" [
{"byteOffset" 4075
"beforeString" "\n\nThe following match of 1024 bytes completely fills half the byte buffer. It is a search substring of the maximum size......\n\n"
"afterString" "\nThe following max-size match straddles a 1024 byte buffer.\nXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
"matchString" pattern
"logviewerURL" (str "http://"
expected-host
":"
expected-port
"/log?file=src%2Fdev%2F"
(.getName file)
"&start=0&length=51200")}
{"byteOffset" 5159
"beforeString" "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX\nThe following max-size match straddles a 1024 byte buffer.\n"
"afterString" "\n\nHere are four non-ascii 1-byte UTF-8 characters: αβγδε\n\nneedle\n\nHere are four printable 2-byte UTF-8 characters: ¡¢£¤"
"matchString" pattern
"logviewerURL" (str "http://"
expected-host
":"
expected-port
"/log?file=src%2Fdev%2F"
(.getName file)
"&start=0&length=51200")}
]}
(logviewer/substring-search file pattern :num-matches 2))))
(let [pattern "𐄀𐄁𐄂"]
(is
(= {"nextByteOffset" 7176
"searchString" pattern
"startByteOffset" 0
"matches" [
{"byteOffset" 7164
"beforeString" "padding 372\npadding 373\npadding 374\npadding 375\n\nThe following tests multibyte UTF-8 Characters straddling the byte boundary: "
"afterString" "\n\nneedle"
"matchString" pattern
"logviewerURL" (str "http://"
expected-host
":"
expected-port
"/log?file=src%2Fdev%2F"
(.getName file)
"&start=0&length=51200")}
]}
(logviewer/substring-search file pattern :num-matches 1))))
(testing "Returns 0 matches for unseen pattern"
(let [pattern "Not There"]
(is (= {"searchString" pattern
"startByteOffset" 0
"matches" []}
(logviewer/substring-search file
pattern
:num-matches nil
:start-byte-offset nil)))))))))
(deftest test-find-n-matches
(testing "find-n-matches looks through logs properly"
(let [files [(clojure.java.io/file "src" "dev" "logviewer-search-context-tests.log")
(clojure.java.io/file "src" "dev" "logviewer-search-context-tests.log.gz")]
matches1 ((logviewer/find-n-matches files 20 0 0 "needle") "matches")
matches2 ((logviewer/find-n-matches files 20 0 126 "needle") "matches")
matches3 ((logviewer/find-n-matches files 20 1 0 "needle") "matches")]
(is (= 2 (count matches1)))
(is (= 4 (count ((first matches1) "matches"))))
(is (= 4 (count ((second matches1) "matches"))))
(is (= ((first matches1) "fileName") "src/dev/logviewer-search-context-tests.log"))
(is (= ((second matches1) "fileName") "src/dev/logviewer-search-context-tests.log.gz"))
(is (= 2 (count ((first matches2) "matches"))))
(is (= 4 (count ((second matches2) "matches"))))
(is (= 1 (count matches3)))
(is (= 4 (count ((first matches3) "matches")))))))
(deftest test-deep-search-logs-for-topology
(let [files [(clojure.java.io/file "src" "dev" "logviewer-search-context-tests.log")
(clojure.java.io/file "src" "dev" "logviewer-search-context-tests.log.gz")]
attrs (make-array FileAttribute 0)
topo-path (.getCanonicalPath (.toFile (Files/createTempDirectory "topoA" attrs)))
_ (.createNewFile (clojure.java.io/file topo-path "6400"))
_ (.createNewFile (clojure.java.io/file topo-path "6500"))
_ (.createNewFile (clojure.java.io/file topo-path "6600"))
_ (.createNewFile (clojure.java.io/file topo-path "6700"))]
(stubbing
[logviewer/logs-for-port files
logviewer/find-n-matches nil]
(testing "deep-search-logs-for-topology all-ports search-archived = true"
(instrumenting
[logviewer/find-n-matches
logviewer/logs-for-port]
(logviewer/deep-search-logs-for-topology "" nil topo-path "search" "20" "*" "20" "199" true nil nil)
(verify-call-times-for logviewer/find-n-matches 4)
(verify-call-times-for logviewer/logs-for-port 4)
; File offset and byte offset should always be zero when searching multiple workers (multiple ports).
(verify-nth-call-args-for 1 logviewer/find-n-matches files 20 0 0 "search")
(verify-nth-call-args-for 2 logviewer/find-n-matches files 20 0 0 "search")
(verify-nth-call-args-for 3 logviewer/find-n-matches files 20 0 0 "search")
(verify-nth-call-args-for 4 logviewer/find-n-matches files 20 0 0 "search")))
(testing "deep-search-logs-for-topology all-ports search-archived = false"
(instrumenting
[logviewer/find-n-matches
logviewer/logs-for-port]
(logviewer/deep-search-logs-for-topology "" nil topo-path "search" "20" nil "20" "199" nil nil nil)
(verify-call-times-for logviewer/find-n-matches 4)
(verify-call-times-for logviewer/logs-for-port 4)
; File offset and byte offset should always be zero when searching multiple workers (multiple ports).
(verify-nth-call-args-for 1 logviewer/find-n-matches [(first files)] 20 0 0 "search")
(verify-nth-call-args-for 2 logviewer/find-n-matches [(first files)] 20 0 0 "search")
(verify-nth-call-args-for 3 logviewer/find-n-matches [(first files)] 20 0 0 "search")
(verify-nth-call-args-for 4 logviewer/find-n-matches [(first files)] 20 0 0 "search")))
(testing "deep-search-logs-for-topology one-port search-archived = true, no file-offset"
(instrumenting
[logviewer/find-n-matches
logviewer/logs-for-port]
(logviewer/deep-search-logs-for-topology "" nil topo-path "search" "20" "6700" "0" "0" true nil nil)
(verify-call-times-for logviewer/find-n-matches 1)
(verify-call-times-for logviewer/logs-for-port 2)
(verify-nth-call-args-for 1 logviewer/find-n-matches files 20 0 0 "search")))
(testing "deep-search-logs-for-topology one-port search-archived = true, file-offset = 1"
(instrumenting
[logviewer/find-n-matches
logviewer/logs-for-port]
(logviewer/deep-search-logs-for-topology "" nil topo-path "search" "20" "6700" "1" "0" true nil nil)
(verify-call-times-for logviewer/find-n-matches 1)
(verify-call-times-for logviewer/logs-for-port 2)
(verify-nth-call-args-for 1 logviewer/find-n-matches files 20 1 0 "search")))
(testing "deep-search-logs-for-topology one-port search-archived = false, file-offset = 1"
(instrumenting
[logviewer/find-n-matches
logviewer/logs-for-port]
(logviewer/deep-search-logs-for-topology "" nil topo-path "search" "20" "6700" "1" "0" nil nil nil)
(verify-call-times-for logviewer/find-n-matches 1)
(verify-call-times-for logviewer/logs-for-port 2)
; File offset should be zero, since search-archived is false.
(verify-nth-call-args-for 1 logviewer/find-n-matches [(first files)] 20 0 0 "search")))
(testing "deep-search-logs-for-topology one-port search-archived = true, file-offset = 1, byte-offset = 100"
(instrumenting
[logviewer/find-n-matches
logviewer/logs-for-port]
(logviewer/deep-search-logs-for-topology "" nil topo-path "search" "20" "6700" "1" "100" true nil nil)
(verify-call-times-for logviewer/find-n-matches 1)
(verify-call-times-for logviewer/logs-for-port 2)
; File offset should be zero, since search-archived is false.
(verify-nth-call-args-for 1 logviewer/find-n-matches files 20 1 100 "search")))
(testing "deep-search-logs-for-topology bad-port search-archived = false, file-offset = 1"
(instrumenting
[logviewer/find-n-matches
logviewer/logs-for-port]
(logviewer/deep-search-logs-for-topology "" nil topo-path "search" "20" "2700" "1" "0" nil nil nil)
; Called with a bad port (not in the config) No searching should be done.
(verify-call-times-for logviewer/find-n-matches 0)
(verify-call-times-for logviewer/logs-for-port 0)))
(rmr topo-path))))