# vim:set ft= ts=4 sw=4 et fdm=marker: use Test::Nginx::Socket::Lua; #worker_connections(1014); #master_on(); #workers(2); #log_level('warn'); repeat_each(5); plan tests => repeat_each() * (blocks() * 2 + 7); our $HtmlDir = html_dir; #no_diff(); #no_long_string(); run_tests(); __DATA__ === TEST 1: gmatch --- config location /re { content_by_lua ' for m in ngx.re.gmatch("hello, world", "[a-z]+") do if m then ngx.say(m[0]) else ngx.say("not matched: ", m) end end '; } --- request GET /re --- response_body hello world === TEST 2: fail to match --- config location /re { content_by_lua ' local it = ngx.re.gmatch("hello, world", "[0-9]") local m = it() if m then ngx.say(m[0]) else ngx.say(m) end local m = it() if m then ngx.say(m[0]) else ngx.say(m) end local m = it() if m then ngx.say(m[0]) else ngx.say(m) end '; } --- request GET /re --- response_body nil nil nil === TEST 3: match but iterate more times (not just match at the end) --- config location /re { content_by_lua ' local it = ngx.re.gmatch("hello, world!", "[a-z]+") local m = it() if m then ngx.say(m[0]) else ngx.say(m) end local m = it() if m then ngx.say(m[0]) else ngx.say(m) end local m = it() if m then ngx.say(m[0]) else ngx.say(m) end local m = it() if m then ngx.say(m[0]) else ngx.say(m) end '; } --- request GET /re --- response_body hello world nil nil === TEST 4: match but iterate more times (just matched at the end) --- config location /re { content_by_lua ' local it = ngx.re.gmatch("hello, world", "[a-z]+") local m = it() if m then ngx.say(m[0]) else ngx.say(m) end local m = it() if m then ngx.say(m[0]) else ngx.say(m) end local m = it() if m then ngx.say(m[0]) else ngx.say(m) end local m = it() if m then ngx.say(m[0]) else ngx.say(m) end '; } --- request GET /re --- response_body hello world nil nil === TEST 5: anchored match (failed) --- config location /re { content_by_lua ' it = ngx.re.gmatch("hello, 1234", "([0-9]+)", "a") ngx.say(it()) '; } --- request GET /re --- response_body nil === TEST 6: anchored match (succeeded) --- config location /re { content_by_lua ' local it = ngx.re.gmatch("12 hello 34", "[0-9]", "a") local m = it() ngx.say(m[0]) m = it() ngx.say(m[0]) ngx.say(it()) '; } --- request GET /re --- response_body 1 2 nil === TEST 7: non-anchored gmatch (without regex cache) --- config location /re { content_by_lua ' local it = ngx.re.gmatch("12 hello 34", "[0-9]") local m = it() ngx.say(m and m[0]) m = it() ngx.say(m and m[0]) m = it() ngx.say(m and m[0]) m = it() ngx.say(m and m[0]) m = it() ngx.say(m and m[0]) '; } --- request GET /re --- response_body 1 2 3 4 nil === TEST 8: non-anchored gmatch (with regex cache) --- config location /re { content_by_lua ' local it = ngx.re.gmatch("12 hello 34", "[0-9]", "o") local m = it() ngx.say(m and m[0]) m = it() ngx.say(m and m[0]) m = it() ngx.say(m and m[0]) m = it() ngx.say(m and m[0]) m = it() ngx.say(m and m[0]) '; } --- request GET /re --- response_body 1 2 3 4 nil === TEST 9: anchored match (succeeded) --- config location /re { content_by_lua ' local it = ngx.re.gmatch("12 hello 34", "[0-9]", "a") local m = it() ngx.say(m[0]) m = it() ngx.say(m[0]) ngx.say(it()) '; } --- request GET /re --- response_body 1 2 nil === TEST 10: anchored match (succeeded, set_by_lua) --- config location /re { set_by_lua $res ' local it = ngx.re.gmatch("12 hello 34", "[0-9]", "a") local m = it() return m[0] '; echo $res; } --- request GET /re --- response_body 1 === TEST 11: gmatch (look-behind assertion) --- config location /re { content_by_lua ' for m in ngx.re.gmatch("{foobar}, {foobaz}", "(?<=foo)ba[rz]") do if m then ngx.say(m[0]) else ngx.say("not matched: ", m) end end '; } --- request GET /re --- response_body bar baz === TEST 12: gmatch (look-behind assertion 2) --- config location /re { content_by_lua ' for m in ngx.re.gmatch("{foobarbaz}", "(?<=foo)bar|(?<=bar)baz") do if m then ngx.say(m[0]) else ngx.say("not matched: ", m) end end '; } --- request GET /re --- response_body bar baz === TEST 13: with regex cache --- config location /re { content_by_lua ' local it = ngx.re.gmatch("hello, 1234", "([A-Z]+)", "io") local m = it() ngx.say(m and m[0]) it = ngx.re.gmatch("1234, okay", "([A-Z]+)", "io") m = it() ngx.say(m and m[0]) it = ngx.re.gmatch("hi, 1234", "([A-Z]+)", "o") m = it() ngx.say(m and m[0]) '; } --- request GET /re --- stap2 F(ngx_http_lua_ngx_re_gmatch_iterator) { println("iterator") } F(ngx_http_lua_ngx_re_gmatch_gc) { println("gc") } F(ngx_http_lua_ngx_re_gmatch_cleanup) { println("cleanup") } --- response_body hello okay nil === TEST 14: exceeding regex cache max entries --- http_config lua_regex_cache_max_entries 2; --- config location /re { content_by_lua ' local it = ngx.re.gmatch("hello, 1234", "([0-9]+)", "o") local m = it() ngx.say(m and m[0]) it = ngx.re.gmatch("howdy, 567", "([0-9]+)", "oi") m = it() ngx.say(m and m[0]) it = ngx.re.gmatch("hiya, 98", "([0-9]+)", "ox") m = it() ngx.say(m and m[0]) '; } --- request GET /re --- response_body 1234 567 98 === TEST 15: disable regex cache completely --- http_config lua_regex_cache_max_entries 0; --- config location /re { content_by_lua ' local it = ngx.re.gmatch("hello, 1234", "([0-9]+)", "o") local m = it() ngx.say(m and m[0]) it = ngx.re.gmatch("howdy, 567", "([0-9]+)", "oi") local m = it() ngx.say(m and m[0]) it = ngx.re.gmatch("hiya, 98", "([0-9]+)", "ox") local m = it() ngx.say(m and m[0]) '; } --- request GET /re --- response_body 1234 567 98 === TEST 16: gmatch matched but no iterate --- config location /re { content_by_lua ' local it = ngx.re.gmatch("hello, world", "[a-z]+") ngx.say("done") '; } --- request GET /re --- response_body done === TEST 17: gmatch matched but only iterate once and still matches remain --- config location /re { content_by_lua ' local it = ngx.re.gmatch("hello, world", "[a-z]+") local m = it() if m then ngx.say(m[0]) else ngx.say("not matched") end '; } --- request GET /re --- response_body hello === TEST 18: gmatch matched but no iterate and early forced GC --- config location /re { content_by_lua ' local a = {} for i = 1, 3 do it = ngx.re.gmatch("hello, world", "[a-z]+") it() collectgarbage() table.insert(a, {"hello", "world"}) end ngx.say("done") '; } --- request GET /re --- response_body done === TEST 19: gmatch iterator used by another request --- http_config eval "lua_package_path '$::HtmlDir/?.lua;;';" --- config location /main { content_by_lua ' package.loaded.foo = nil local res = ngx.location.capture("/t") if res.status == 200 then ngx.print(res.body) else ngx.say("sr failed: ", res.status) end res = ngx.location.capture("/t") if res.status == 200 then ngx.print(res.body) else ngx.say("sr failed: ", res.status) end '; } location /t { content_by_lua ' local foo = require "foo" local m = foo.go() ngx.say(m and "matched" or "no") '; } --- user_files >>> foo.lua module("foo", package.seeall) local it function go() if not it then it = ngx.re.gmatch("hello, world", "[a-z]+") end return it() end --- request GET /main --- response_body matched sr failed: 500 --- error_log attempt to use ngx.re.gmatch iterator in a request that did not create it === TEST 20: gmatch (empty matched string) --- config location /re { content_by_lua ' for m in ngx.re.gmatch("hello", "a|") do if m then ngx.say("matched: [", m[0], "]") else ngx.say("not matched: ", m) end end '; } --- request GET /re --- response_body matched: [] matched: [] matched: [] matched: [] matched: [] matched: [] === TEST 21: gmatch with named pattern --- config location /re { content_by_lua ' local it = ngx.re.gmatch("1234, 1234", "(?[0-9]+)") m = it() if m then ngx.say(m[0]) ngx.say(m[1]) ngx.say(m["first"]) else ngx.say("not matched!") end m = it() if m then ngx.say(m[0]) ngx.say(m[1]) ngx.say(m["first"]) else ngx.say("not matched!") end '; } --- request GET /re --- response_body 1234 1234 1234 1234 1234 1234 === TEST 22: gmatch with multiple named pattern --- config location /re { content_by_lua ' local it = ngx.re.gmatch("1234, abcd, 1234", "(?[0-9]+)|(?[a-z]+)") m = it() if m then ngx.say(m[0]) ngx.say(m[1]) ngx.say(m[2]) ngx.say(m["first"]) ngx.say(m["second"]) else ngx.say("not matched!") end m = it() if m then ngx.say(m[0]) ngx.say(m[1]) ngx.say(m[2]) ngx.say(m["first"]) ngx.say(m["second"]) else ngx.say("not matched!") end '; } --- request GET /re --- response_body 1234 1234 false 1234 false abcd false abcd false abcd === TEST 23: gmatch with duplicate named pattern w/ extraction --- config location /re { content_by_lua ' local it = ngx.re.gmatch("hello, 1234", "(?[a-z]+), (?[0-9]+)", "D") m = it() if m then ngx.say(m[0]) ngx.say(m[1]) ngx.say(m[2]) ngx.say(table.concat(m.first,"-")) else ngx.say("not matched!") end m = it() if m then ngx.say(m[0]) ngx.say(m[1]) ngx.say(m[2]) ngx.say(table.concat(m.first,"-")) else ngx.say("not matched!") end '; } --- request GET /re --- response_body hello, 1234 hello 1234 hello-1234 not matched! === TEST 24: named captures are empty --- config location /re { content_by_lua ' local it = ngx.re.gmatch("1234", "(?[a-z]*)([0-9]+)", "") local m = it() if m then ngx.say(m[0]) ngx.say(m.first) ngx.say(m[1]) ngx.say(m[2]) else ngx.say("not matched!") end '; } --- request GET /re --- response_body 1234 1234 === TEST 25: named captures are empty (with regex cache) --- config location /re { content_by_lua ' local it = ngx.re.gmatch("1234", "(?[a-z]*)([0-9]+)", "o") local m = it() if m then ngx.say(m[0]) ngx.say(m.first) ngx.say(m[1]) ngx.say(m[2]) else ngx.say("not matched!") end '; } --- request GET /re --- response_body 1234 1234 === TEST 26: bad pattern --- config location /re { content_by_lua ' local it, err = ngx.re.gmatch("hello\\nworld", "(abc") if not err then ngx.say("good") else ngx.say("error: ", err) end '; } --- request GET /re --- response_body error: pcre_compile() failed: missing ) in "(abc" --- no_error_log [error] === TEST 27: bad UTF-8 --- config location = /t { content_by_lua ' local target = "你好" local regex = "你好" -- Note the D here local it, err = ngx.re.gmatch(string.sub(target, 1, 4), regex, "u") if err then ngx.say("error: ", err) return end local m, err = it() if err then ngx.say("error: ", err) return end if m then ngx.say("matched: ", m[0]) else ngx.say("not matched") end '; } --- request GET /t --- response_body_like chop error: pcre_exec\(\) failed: -10 --- no_error_log [error] === TEST 28: UTF-8 mode without UTF-8 sequence checks --- config location /re { content_by_lua ' local it = ngx.re.gmatch("你好", ".", "U") local m = it() if m then ngx.say(m[0]) else ngx.say("not matched!") end '; } --- stap probe process("$LIBPCRE_PATH").function("pcre_compile") { printf("compile opts: %x\n", $options) } probe process("$LIBPCRE_PATH").function("pcre_exec") { printf("exec opts: %x\n", $options) } --- stap_out compile opts: 800 exec opts: 2000 --- request GET /re --- response_body 你 --- no_error_log [error] === TEST 29: UTF-8 mode with UTF-8 sequence checks --- config location /re { content_by_lua ' local it = ngx.re.gmatch("你好", ".", "u") local m = it() if m then ngx.say(m[0]) else ngx.say("not matched!") end '; } --- stap probe process("$LIBPCRE_PATH").function("pcre_compile") { printf("compile opts: %x\n", $options) } probe process("$LIBPCRE_PATH").function("pcre_exec") { printf("exec opts: %x\n", $options) } --- stap_out compile opts: 800 exec opts: 0 --- request GET /re --- response_body 你 --- no_error_log [error] === TEST 30: just hit match limit --- http_config lua_regex_match_limit 5000; --- config location /re { content_by_lua_file html/a.lua; } --- user_files >>> a.lua local re = [==[(?i:([\s'\"`´’‘\(\)]*)?([\d\w]+)([\s'\"`´’‘\(\)]*)?(?:=|<=>|r?like|sounds\s+like|regexp)([\s'\"`´’‘\(\)]*)?\2|([\s'\"`´’‘\(\)]*)?([\d\w]+)([\s'\"`´’‘\(\)]*)?(?:!=|<=|>=|<>|<|>|\^|is\s+not|not\s+like|not\s+regexp)([\s'\"`´’‘\(\)]*)?(?!\6)([\d\w]+))]==] s = string.rep([[ABCDEFG]], 10) local start = ngx.now() local it, err = ngx.re.gmatch(s, re, "o") if not it then ngx.say("failed to gen iterator: ", err) return end local res, err = it() --[[ ngx.update_time() local elapsed = ngx.now() - start ngx.say(elapsed, " sec elapsed.") ]] if not res then if err then ngx.say("error: ", err) return end ngx.say("failed to match") return end --- request GET /re --- response_body error: pcre_exec() failed: -8 === TEST 31: just not hit match limit --- http_config lua_regex_match_limit 5100; --- config location /re { content_by_lua_file html/a.lua; } --- user_files >>> a.lua local re = [==[(?i:([\s'\"`´’‘\(\)]*)?([\d\w]+)([\s'\"`´’‘\(\)]*)?(?:=|<=>|r?like|sounds\s+like|regexp)([\s'\"`´’‘\(\)]*)?\2|([\s'\"`´’‘\(\)]*)?([\d\w]+)([\s'\"`´’‘\(\)]*)?(?:!=|<=|>=|<>|<|>|\^|is\s+not|not\s+like|not\s+regexp)([\s'\"`´’‘\(\)]*)?(?!\6)([\d\w]+))]==] s = string.rep([[ABCDEFG]], 10) local start = ngx.now() local it, err = ngx.re.gmatch(s, re, "o") if not it then ngx.say("failed to gen iterator: ", err) return end res, err = it() --[[ ngx.update_time() local elapsed = ngx.now() - start ngx.say(elapsed, " sec elapsed.") ]] if not res then if err then ngx.say("error: ", err) return end ngx.say("failed to match") return end --- request GET /re --- response_body failed to match