fluent-plugin-websphere-iib 1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 23a31f4344dea2d0a5ad8c145b6c9ba5358d0706
4
+ data.tar.gz: 95376f4b90e6408e2ef9d51f54f6818c88231a73
5
+ SHA512:
6
+ metadata.gz: f94732be82fadf093b4ca71275adabff2d322922f3d78d94aadbd473eb4a3d43527f3b915c8865df8eca7fe7e6e21ebc783a3cf54864eb19152523a83db6d5a2
7
+ data.tar.gz: e264f933eb08a3ef4c89324dadbd325edc1dfc5bb20d498e8c76b42a39d2c5116d367edf7c3d4705fb05e35d80da54a476dc9b4dc9d20fdea1d16c2947b6fa32
@@ -0,0 +1,39 @@
1
+ fluent-plugin-websphere-iib
2
+ ===========================
3
+
4
+ fluentd plugin for parsing IBM websphere IIB logs inthe syslog /var/log/messages
5
+
6
+ #Available format Plugins:
7
+ * websphere_iib_syslog: format logs in /var/log/messages for IBM websphere IIB
8
+ * websphere_iib_stdout: format logs in stdout for IBM websphere IIB
9
+
10
+ #Plugin Settings:
11
+ Both plugins have the same configuration options:
12
+
13
+ * remote_syslog: fqdn or ip of the remote syslog instance
14
+ * port: the port, where the remote syslog instance is listening
15
+ * hostname: hostname to be set for syslog messages
16
+ * remove_tag_prefix: remove tag prefix for tag placeholder.
17
+ * tag_key: use the field specified in tag_key from record to set the syslog key
18
+ * facility: Syslog log facility
19
+ * severity: Syslog log severity
20
+ * use_record: Use severity and facility from record if available
21
+ * payload_key: Use the field specified in payload_key from record to set payload
22
+
23
+ #Configuration example:
24
+ ```
25
+ <match site.*>
26
+ type syslog_buffered
27
+ remote_syslog your.syslog.host
28
+ port 25
29
+ hostname ${hostname}
30
+ facility local0
31
+ severity debug
32
+ </match>
33
+ ```
34
+
35
+
36
+ Contributors:
37
+
38
+ * Victor Guillen
39
+ * [superguillen](http://github.com/superguillen)
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
3
+
4
+ require 'rake/testtask'
5
+ Rake::TestTask.new(:test) do |test|
6
+ test.libs << 'lib' << 'test'
7
+ test.pattern = 'test/**/test_*.rb'
8
+ test.verbose = true
9
+ end
10
+
11
+ task :default => :test
@@ -0,0 +1,206 @@
1
+ module Fluent
2
+ class TextParser
3
+ class WebsphereSysout < Parser
4
+ Plugin.register_parser("multiline_websphere_iib", self)
5
+
6
+ config_param :time_format, :string, :default =>'%m/%d/%y %H:%M:%S:%L %Z'
7
+ config_param :output_time_format, :string, :default =>'%Y-%m-%dT%H:%M:%S.%L%z'
8
+ config_param :format_firstline, :string, :default =>'/\[\d{1,2}\/\d{1,2}\/\d{2,4} \d{1,2}:\d{1,2}:\d{1,2}:\d{1,3}\s.{3}\]/'
9
+ config_param :format1, :string, :default =>'/\[(?<timestamp>\d{1,2}\/\d{1,2}\/\d{2,4} \d{1,2}:\d{1,2}:\d{1,2}:\d{1,3}\s.{3})\]\s+(?<treadid>\S+)\s+(?<msgshortname>\S+)\s+(?<eventype>\S+)\s+(?<msgid>\S+)\s*(?<message>.*)/'
10
+ config_param :ambiente, :string, :default =>'unknow'
11
+ config_param :producto, :string, :default =>'unknow'
12
+ config_param :tipolog, :string, :default =>'websphere.integration_bus'
13
+ config_param :integration_node, :string, :default =>'unknow'
14
+ config_param :integration_server, :string, :default =>'unknow'
15
+
16
+ REGEXP_PMRM0003I = '^.*type=(?<type>.+)\s+detail=(?<detail>.+)\s+elapsed=(?<elapsed>\S+)$'
17
+ FORMAT_MAX_NUM = 20
18
+
19
+ def initialize
20
+ super
21
+ @mutex = Mutex.new
22
+ @regexp_pmrm0003i = Regexp.new(REGEXP_PMRM0003I)
23
+ end
24
+
25
+ def configure(conf)
26
+ super
27
+
28
+ conf["format1"] ||= @format1
29
+
30
+ $log.info "format1: -> "+@format1
31
+
32
+ formats = parse_formats(conf).compact.map { |f| f[1..-2] }.join
33
+ $log.info "formats: -> "+formats
34
+ begin
35
+ @regex = Regexp.new(formats, Regexp::MULTILINE)
36
+ if @regex.named_captures.empty?
37
+ raise "No named captures"
38
+ end
39
+ @parser = RegexpParser.new(@regex, conf)
40
+ rescue => e
41
+ raise ConfigError, "Invalid regexp '#{formats}': #{e}"
42
+ end
43
+
44
+ if @format_firstline
45
+ check_format_regexp(@format_firstline, 'format_firstline')
46
+ @firstline_regex = Regexp.new(@format_firstline[1..-2])
47
+ end
48
+ end
49
+
50
+ def parse(text, &block)
51
+ m = @regex.match(text)
52
+
53
+ unless m
54
+ if block_given?
55
+ yield nil, nil
56
+ return
57
+ else
58
+ return nil, nil
59
+ end
60
+ end
61
+
62
+ record = {}
63
+ record["tipolog"] = @tipolog
64
+ record["integration_node"] = @integration_node
65
+ record["integration_server"] = @integration_server
66
+ record["producto"] = @producto
67
+ record["ambiente"] = @ambiente
68
+ record["eventype"] = "INFO"
69
+ record["severity"] = "LOW"
70
+ record["severity_level"] = 5
71
+ record["hostname"] = Socket.gethostname
72
+
73
+ m.names.each { |name|
74
+ if value = m[name]
75
+ case name
76
+ when "timestamp"
77
+ #Se calcula timestmap adicionando timezone
78
+ timestamp = @mutex.synchronize { DateTime.strptime(value,@time_format).strftime(@output_time_format) }
79
+ time = @mutex.synchronize { DateTime.strptime(value,@time_format).to_time.to_i }
80
+ #$log.info "timestamp: #{value+@timezone_offset}"
81
+ record[name] = timestamp
82
+ when "eventype"
83
+ record[name] = value
84
+ case record[name]
85
+ when "I"
86
+ record["eventype"] = "INFO"
87
+ record["severity"] = "LOW"
88
+ record["severity_level"] = 5
89
+ when "D"
90
+ record["eventype"] = "DETAIL"
91
+ record["severity"] = "LOW"
92
+ record["severity_level"] = 6
93
+ when "E"
94
+ record["eventype"] = "ERROR"
95
+ record["severity"] ="HIGH"
96
+ record["severity_level"] = 5
97
+ when "W"
98
+ record["eventype"] = "WARNING"
99
+ record["severity"] = "MEDIUM"
100
+ record["severity_level"] = 5
101
+ when "F"
102
+ record["eventype"] = "FATAL"
103
+ record["severity"] = "HIGH"
104
+ record["severity_level"] = 4
105
+ when "C"
106
+ record["eventype"] = "CONFIGURATION"
107
+ record["severity"] = "MEDIUM"
108
+ record["severity_level"] = 5
109
+ when "O"
110
+ record["eventype"] = "SYSTEM_OUTPUT"
111
+ record["severity"] = "LOW"
112
+ record["severity_level"] = 5
113
+ when "R"
114
+ record["eventype"] = "SYSTEM_ERROR"
115
+ record["severity"] = "LOW"
116
+ record["severity_level"] = 5
117
+ when "Z"
118
+ record["eventype"] = "NOT_RECOGNIZED"
119
+ record["severity"] = "LOW"
120
+ record["severity_level"] = 5
121
+ end
122
+
123
+ when "message"
124
+ case record["msgid"]
125
+ when "PMRM0003I:"
126
+ msg = value
127
+ #Se extrae datos de request metrics
128
+ if requestMetrics = @regexp_pmrm0003i.match(msg)
129
+ record["type"] = requestMetrics["type"]
130
+ record["detail"] = requestMetrics["detail"]
131
+ record["elapsed"] = requestMetrics["elapsed"]
132
+ record["tipolog"] = 'requestmetrics.'+record["tipolog"]
133
+ record["mesage"].delete
134
+ end
135
+ else
136
+ record[name] = value
137
+ end
138
+ else
139
+ record[name] = value
140
+ end
141
+ end
142
+ }
143
+
144
+ if @estimate_current_event
145
+ time ||= Engine.now
146
+ end
147
+
148
+ if block_given?
149
+ yield time, record
150
+ else
151
+ return time, record
152
+ end
153
+
154
+ end
155
+
156
+ def has_firstline?
157
+ !!@format_firstline
158
+ end
159
+
160
+ def firstline?(text)
161
+ @firstline_regex.match(text)
162
+ end
163
+
164
+ private
165
+
166
+ def parse_formats(conf)
167
+ check_format_range(conf)
168
+
169
+ prev_format = nil
170
+ (1..FORMAT_MAX_NUM).map { |i|
171
+ format = conf["format#{i}"]
172
+ if (i > 1) && prev_format.nil? && !format.nil?
173
+ raise ConfigError, "Jump of format index found. format#{i - 1} is missing."
174
+ end
175
+ prev_format = format
176
+ next if format.nil?
177
+
178
+ check_format_regexp(format, "format#{i}")
179
+ format
180
+ }
181
+ end
182
+
183
+ def check_format_range(conf)
184
+ invalid_formats = conf.keys.select { |k|
185
+ m = k.match(/^format(\d+)$/)
186
+ m ? !((1..FORMAT_MAX_NUM).include?(m[1].to_i)) : false
187
+ }
188
+ unless invalid_formats.empty?
189
+ raise ConfigError, "Invalid formatN found. N should be 1 - #{FORMAT_MAX_NUM}: " + invalid_formats.join(",")
190
+ end
191
+ end
192
+
193
+ def check_format_regexp(format, key)
194
+ if format[0] == '/' && format[-1] == '/'
195
+ begin
196
+ Regexp.new(format[1..-2], Regexp::MULTILINE)
197
+ rescue => e
198
+ raise ConfigError, "Invalid regexp in #{key}: #{e}"
199
+ end
200
+ else
201
+ raise ConfigError, "format should be Regexp, need //, in #{key}: '#{format}'"
202
+ end
203
+ end
204
+ end
205
+ end
206
+ end
@@ -0,0 +1,130 @@
1
+ module Fluent
2
+ class TextParser
3
+ class SyslogParserCustom < Parser
4
+ Plugin.register_parser("syslogcustom", self)
5
+ # From existence TextParser pattern
6
+ REGEXP = '^(?<timestamp>[^ ]*\s*[^ ]* [^ ]*) (?<hostname>[^ ]*) (?<identificador>[a-zA-Z-1-9_\/\.\-]*)(?:\[(?<pid>[0-9]+)\])?(?:[^\:]*\:)? *(?<message>.*)$'
7
+ # From in_syslog default pattern
8
+ REGEXP_WITH_PRI = '^\<(?<priority>[0-9]+)\>(?<timestamp>[^ ]* {1,2}[^ ]* [^ ]*) (?<hostname>[^ ]*) (?<identificador>[a-zA-Z0-9_\/\.\-]*)(?:\[(?<pid>[0-9]+)\])?(?:[^\:]*\:)? *(?<message>.*)$'
9
+ #Expresion regular cuando esta presente el log del IIB
10
+ IIB_REGEXP = '^(?<product_name>[^\(]+)\((?<nodo>[^\(]+)\) \[Thread(?<thread>[^\[]+)\] \(Msg (?<msg>[^\(]+)\) (?<msgid>[^\:]+)\: (?<message>.*)$'
11
+
12
+ config_param :time_format, :string, :default => "%b %d %H:%M:%S"
13
+ #Incluye timezone (se agrega a la fecha de entrada)
14
+ config_param :output_time_format, :string, :default => "%Y-%m-%dT%H:%M:%S.%L%z"
15
+ config_param :with_priority, :bool, :default => false
16
+ config_param :keep_time_key, :bool, :default => true
17
+ config_param :ambiente, :string, :default => nil
18
+
19
+ def initialize
20
+ super
21
+ @mutex = Mutex.new
22
+ end
23
+
24
+ def configure(conf)
25
+ super
26
+
27
+ require 'active_support/time'
28
+
29
+ @timezone_offset = Time.now.formatted_offset
30
+ @regexp = @with_priority ? Regexp.new(REGEXP_WITH_PRI) : Regexp.new(REGEXP)
31
+ @iib_regexp = Regexp.new(IIB_REGEXP)
32
+ @time_parser = TextParser::TimeParser.new(@time_format)
33
+ end
34
+
35
+ def patterns
36
+ {'format' => @regexp, 'time_format' => @time_format, 'subformat' => @iib_regexp}
37
+ end
38
+
39
+ def parse(text)
40
+ m = @regexp.match(text)
41
+ #n = @iib_regexp.match(text)
42
+ unless m
43
+ if block_given?
44
+ yield nil, nil
45
+ return
46
+ else
47
+ return nil, nil
48
+ end
49
+ end
50
+
51
+ time = nil
52
+ msg = nil
53
+
54
+ record = {}
55
+ record["eventype"] = "INFO"
56
+ record["severity"] = "LOW"
57
+ record["severity_level"] = 4
58
+ record["hostname"] = Socket.gethostname
59
+ m.names.each { |name|
60
+ if value = m[name]
61
+ #$log.info ">>>>>>: #{name}"
62
+ case name
63
+ when "priority"
64
+ record['priority'] = value.to_i
65
+ when "message"
66
+ case record["identificador"]
67
+ when "IIB"
68
+ #$log.info "message: -> #{value}"
69
+ msg = value
70
+ n = @iib_regexp.match(msg)
71
+ n.names.each { |name|
72
+ if msg = n[name]
73
+ #$log.info ">>>>>>: #{name}"
74
+ record[name] = msg
75
+ end
76
+ }
77
+
78
+ if record.has_key?("nodo")
79
+ record["integration_node"] = record["nodo"].split(".")[0]
80
+ record["integration_server"] = record["nodo"].split(".")[1]
81
+ record.delete("nodo")
82
+ end
83
+
84
+ record["producto"] = record["identificador"]
85
+ record["ambiente"] = @ambiente
86
+ record.delete("identificador")
87
+ record["msgshortname"] = record["msgid"]
88
+ record["eventype"] = record["msgid"][-1]
89
+ case record["eventype"]
90
+ when "E"
91
+ record["eventype"] = "ERROR"
92
+ record["severity"] ="HIGH"
93
+ record["severity_level"] = 5
94
+ when "W"
95
+ record["eventype"] = "WARNING"
96
+ record["severity"] = "MEDIUM"
97
+ record["severity_level"] = 5
98
+ else
99
+ record["eventype"] = "INFO"
100
+ record["severity"] = "LOW"
101
+ record["severity_level"] = 5
102
+ end
103
+ else
104
+ record[name] = value
105
+ end
106
+ when "timestamp"
107
+ time = @mutex.synchronize { @time_parser.parse(value.gsub(/ +/, ' ')) }
108
+ #Se calcula timestmap adicionando timezone
109
+ timestamp = @mutex.synchronize { DateTime.strptime(value+@timezone_offset,@time_format+'%z').strftime(@output_time_format) }
110
+ #$log.info "timestamp: #{value+@timezone_offset}"
111
+ record[name] = timestamp
112
+ else
113
+ record[name] = value
114
+ end
115
+ end
116
+ }
117
+
118
+ if @estimate_current_event
119
+ time ||= Engine.now
120
+ end
121
+
122
+ if block_given?
123
+ yield time, record
124
+ else
125
+ return time, record
126
+ end
127
+ end
128
+ end
129
+ end #textParser
130
+ end
@@ -0,0 +1,30 @@
1
+ require 'rubygems'
2
+ require 'bundler'
3
+ begin
4
+ Bundler.setup(:default, :development)
5
+ rescue Bundler::BundlerError => e
6
+ $stderr.puts e.message
7
+ $stderr.puts "Run `bundle install` to install missing gems"
8
+ exit e.status_code
9
+ end
10
+ require 'test/unit'
11
+
12
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
13
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
14
+ require 'fluent/test'
15
+ unless ENV.has_key?('VERBOSE')
16
+ nulllogger = Object.new
17
+ nulllogger.instance_eval {|obj|
18
+ def method_missing(method, *args)
19
+ # pass
20
+ end
21
+ }
22
+ $log = nulllogger
23
+ end
24
+
25
+ require 'fluentd/plugin/out_syslog'
26
+ require 'fluentd/plugin/out_syslog_buffered'
27
+
28
+
29
+ class Test::Unit::TestCase
30
+ end
@@ -0,0 +1,138 @@
1
+ require 'helper'
2
+
3
+ class SyslogOutputTest < Test::Unit::TestCase
4
+ def setup
5
+ Fluent::Test.setup
6
+ end
7
+
8
+ CONFIG = %[
9
+ remote_syslog 127.0.0.1
10
+ port 25
11
+ hostname testhost
12
+ remove_tag_prefix test
13
+ severity debug
14
+ facility user
15
+ payload_key message
16
+ ]
17
+
18
+ def create_driver(conf=CONFIG,tag='test')
19
+ Fluent::Test::OutputTestDriver.new(Fluent::SyslogOutput, tag).configure(conf)
20
+ end
21
+
22
+ def test_configure
23
+ assert_raise(Fluent::ConfigError) {
24
+ d = create_driver('')
25
+ }
26
+ assert_raise(Fluent::ConfigError) {
27
+ d = create_driver %[
28
+ hostname testhost
29
+ remove_tag_prefix test
30
+ ]
31
+ }
32
+ assert_nothing_raised {
33
+ d = create_driver %[
34
+ remote_syslog 127.0.0.1
35
+ ]
36
+ }
37
+ assert_nothing_raised {
38
+ d = create_driver %[
39
+ remote_syslog 127.0.0.1
40
+ port 639
41
+ ]
42
+ }
43
+ assert_nothing_raised {
44
+ d = create_driver %[
45
+ remote_syslog 127.0.0.1
46
+ port 25
47
+ hostname deathstar
48
+ ]
49
+ }
50
+ assert_nothing_raised {
51
+ d = create_driver %[
52
+ remote_syslog 127.0.0.1
53
+ port 25
54
+ hostname testhost
55
+ remove_tag_prefix test123
56
+ ]
57
+ }
58
+ assert_nothing_raised {
59
+ d = create_driver %[
60
+ remote_syslog 127.0.0.1
61
+ port 25
62
+ hostname testhost
63
+ remove_tag_prefix test
64
+ tag_key tagtag
65
+ severity debug
66
+ ]
67
+ }
68
+ assert_nothing_raised {
69
+ d = create_driver %[
70
+ remote_syslog 127.0.0.1
71
+ port 25
72
+ hostname testhost
73
+ remove_tag_prefix test
74
+ tag_key tagtag
75
+ severity debug
76
+ facility user
77
+ ]
78
+ }
79
+ assert_nothing_raised {
80
+ d = create_driver %[
81
+ remote_syslog 127.0.0.1
82
+ port 25
83
+ hostname testhost
84
+ remove_tag_prefix test
85
+ tag_key tagtag
86
+ severity debug
87
+ facility user
88
+ payload_key message
89
+ ]
90
+ }
91
+ d = create_driver %[
92
+ remote_syslog 127.0.0.1
93
+ port 25
94
+ hostname testhost
95
+ remove_tag_prefix test
96
+ tag_key tagtag
97
+ severity debug
98
+ facility user
99
+ payload_key message
100
+ ]
101
+ assert_equal 25, d.instance.port
102
+ assert_equal "127.0.0.1", d.instance.remote_syslog
103
+ assert_equal "testhost", d.instance.hostname
104
+ assert_equal Regexp.new('^' + Regexp.escape("test")), d.instance.remove_tag_prefix
105
+ assert_equal "tagtag", d.instance.tag_key
106
+ assert_equal "debug", d.instance.severity
107
+ assert_equal "user", d.instance.facility
108
+ assert_equal "message", d.instance.payload_key
109
+
110
+ end
111
+ def test_emit
112
+ d1 = create_driver(CONFIG, 'test.in')
113
+ d1.run do
114
+ d1.emit({'message' => 'asd asd'})
115
+ d1.emit({'message' => 'dsa xasd'})
116
+ d1.emit({'message' => 'ddd ddddd'})
117
+ d1.emit({'message' => '7sssss8 ssssdasd'})
118
+ d1.emit({'message' => 'aaassddffg asdasdasfasf'})
119
+ end
120
+ assert_equal 0, d1.emits.size
121
+
122
+ end
123
+
124
+ def test_emit_with_time_and_without_time
125
+ d1 = create_driver(CONFIG, 'test.in')
126
+ d1.run do
127
+ d1.emit({'message' => 'asd asd', 'time' => '2007-01-31 12:22:26'})
128
+ d1.emit({'message' => 'dsa xasd'})
129
+ d1.emit({'message' => 'ddd ddddd', 'time' => '2007-03-01 12:22:26'})
130
+ d1.emit({'message' => '7sssss8 ssssdasd', 'time' => '2011-03-01 12:22:26'})
131
+ d1.emit({'message' => 'aaassddffg asdasdasfasf', 'time' => '2016-03-01 12:22:26'})
132
+ end
133
+ assert_equal 0, d1.emits.size
134
+
135
+ end
136
+
137
+
138
+ end
metadata ADDED
@@ -0,0 +1,77 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: fluent-plugin-websphere-iib
3
+ version: !ruby/object:Gem::Version
4
+ version: '1.0'
5
+ platform: ruby
6
+ authors:
7
+ - superguillen
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2016-09-25 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: fluentd
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 0.10.45
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 0.10.45
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: 0.9.2
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: 0.9.2
41
+ description: Input plugin for websphere Integration Bus syslog
42
+ email: superguillen.public@gmail.com
43
+ executables: []
44
+ extensions: []
45
+ extra_rdoc_files: []
46
+ files:
47
+ - README.md
48
+ - Rakefile
49
+ - lib/fluentd/plugin/parser_multiline_websphere_iib_stdout.rb
50
+ - lib/fluentd/plugin/parser_websphere_iib_syslog.rb
51
+ - test/helper.rb
52
+ - test/plugin/test_out_syslog.rb
53
+ homepage: https://github.com/superguillen/fluent-plugin-websphere-iib
54
+ licenses:
55
+ - MIT
56
+ metadata: {}
57
+ post_install_message:
58
+ rdoc_options: []
59
+ require_paths:
60
+ - lib
61
+ required_ruby_version: !ruby/object:Gem::Requirement
62
+ requirements:
63
+ - - ">="
64
+ - !ruby/object:Gem::Version
65
+ version: '0'
66
+ required_rubygems_version: !ruby/object:Gem::Requirement
67
+ requirements:
68
+ - - ">="
69
+ - !ruby/object:Gem::Version
70
+ version: '0'
71
+ requirements: []
72
+ rubyforge_project:
73
+ rubygems_version: 2.4.8
74
+ signing_key:
75
+ specification_version: 4
76
+ summary: Input plugin for websphere Integration Bus syslog
77
+ test_files: []