light_mapper 0.0.2 → 1.0.4

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 739e3317c888c4ead5db06449dee80bf264cff23
4
- data.tar.gz: 353f10793c1cd990659934465b064fe035771f55
2
+ SHA256:
3
+ metadata.gz: 309f037da546bc788d238d3fd43c7f92f1d117a1b885a30b7eaf65dc2947a445
4
+ data.tar.gz: f7a0c2c83eb49ce56ccbf1da8b102bf4325f868d9c35dee3a2dc36377f6c96f2
5
5
  SHA512:
6
- metadata.gz: 6748d296cb4d18fa6c2f49e6777ec4e269e5eb19c961baa6e57677d8014958aa59c8464cbda208da8ff8b5b5a975974bdfffd86eaf9e6ae3ad00a68f619c8f68
7
- data.tar.gz: b52174375c53a98b10c09664096fa01ee0a6ec18e841c06a0f12571cd704580c48935c907faec8b32dd77aa6e60a05b518a969ff986f9e3d5e55a62db339131c
6
+ metadata.gz: 9c306788435e091828219174645e0034c4bdfa8a096643d506e34258831bbefbbccf5dca2b0595720207796c301b01dc988abbd312d04391a9c0a5fc2b2d5dc2
7
+ data.tar.gz: 0fbd144a196b3edc3110c81e945d1d7ed6880f7d5cd8b82d34d1754bbdb3a32d7069254b03d5f8d977118c182fe8446734f555da92a144f130029c55c391ecce
data/.rubocop.yml CHANGED
@@ -15,10 +15,6 @@ Style/Documentation:
15
15
  Style/PercentLiteralDelimiters:
16
16
  Enabled: false
17
17
 
18
- # Offense count: 2
19
- Style/RegexpLiteral:
20
- MaxSlashes: 0
21
-
22
18
  # Offense count: 1
23
19
  # Cop supports --auto-correct.
24
20
  # Configuration parameters: EnforcedStyle, SupportedStyles.
@@ -45,4 +41,4 @@ Metrics/LineLength:
45
41
  AllCops:
46
42
  Exclude:
47
43
  - '**/Guardfile'
48
- - '**/light_parmas.gemspec'
44
+ - '**/light_mapper.gemspec'
data/.travis.yml CHANGED
@@ -1,6 +1,4 @@
1
1
  language: ruby
2
2
  script: bundle exec rake
3
3
  rvm:
4
- - 2.0.0
5
- - 2.1.0
6
- - 2.1.4
4
+ - 3.1.2
data/Gemfile CHANGED
@@ -2,3 +2,5 @@ source 'https://rubygems.org'
2
2
 
3
3
  # Specify your gem's dependencies in light_mapper.gemspec
4
4
  gemspec
5
+
6
+ ruby '2.7.4'
data/README.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # LightMapper
2
2
 
3
+ [![Gem Version](https://badge.fury.io/rb/light_mapper.svg)](http://badge.fury.io/rb/light_mapper)
4
+ [![Build Status](https://travis-ci.org/pniemczyk/light_mapper.svg?branch=0.2.0)](https://travis-ci.org/pniemczyk/light_mapper)
5
+ [![Dependency Status](https://gemnasium.com/pniemczyk/light_mapper.svg)](https://gemnasium.com/pniemczyk/light_mapper)
6
+
3
7
  This is simple mapper for hash.
4
8
 
5
9
  ## Installation
@@ -31,6 +35,16 @@ Or install it yourself as:
31
35
  'LastName' => :last_name
32
36
  )
33
37
  ```
38
+
39
+ or
40
+
41
+ ```ruby
42
+ LightMapper.mapping(
43
+ { 'FirstName' => 'Pawel', 'LastName' => 'Niemczyk' },
44
+ { 'FirstName' => :first_name, 'LastName' => :last_name }
45
+ )
46
+ ```
47
+
34
48
  result is obvious:
35
49
 
36
50
  ```ruby
@@ -53,20 +67,16 @@ data = {
53
67
  }
54
68
 
55
69
  data.extend(LightMapper).mapping(PersonMapper)
70
+ # {first_name: 'Pawel', last_name: 'Niemczyk', 'age': 5}
56
71
  ```
57
72
 
58
73
  ### When you require all keys
59
74
 
60
75
  ```ruby
61
- {
62
- 'FirstName' => 'Pawel'
63
- }.extend(LightMapper, require_keys: true).mapping(
64
- 'FirstName' => :first_name,
65
- 'LastName' => :last_name
66
- )
76
+ { 'FirstName' => 'Pawel' }.extend(LightMapper).mapping({'FirstName' => :first_name, 'LastName' => :last_name}, strict: true)
67
77
  ```
68
78
 
69
- it will raise KeyError
79
+ it will raise LightMapper::KeyMissing: LastName key not found; Full path LastName
70
80
 
71
81
  ### When you want to pass string or symbol keys
72
82
 
@@ -74,10 +84,10 @@ it will raise KeyError
74
84
  {
75
85
  'FirstName' => 'Pawel',
76
86
  second_name: 'Niemczyk'
77
- }.extend(LightMapper, any_keys_kind: true).mapping(
78
- 'FirstName' => :first_name,
79
- 'second_name' => :last_name
80
- )
87
+ }.extend(LightMapper).mapping({
88
+ 'FirstName' => :first_name,
89
+ 'second_name' => :last_name
90
+ }, any_keys: true)
81
91
  ```
82
92
 
83
93
  result will be:
@@ -86,6 +96,81 @@ result will be:
86
96
  { first_name: 'Pawel', last_name: 'Niemczyk' }
87
97
  ```
88
98
 
99
+ ### Support for nested hashes, arrays and objects (now we talking what it is capable of)
100
+
101
+ ```ruby
102
+ {
103
+ 'source' => { 'google' => { 'search_word' => 'ruby' } },
104
+ 'user' => User.new(email: 'pawel@example.com', name: 'Pawel'),
105
+ 'roles' => %w[admin manager user],
106
+ 'mixed' => { users: [User.new(email: 'max@example.com', name: 'Max', manager: true), User.new(email: 'pawel@example.com', name: 'Pawel', manager: false)] },
107
+ 'scores' => [ 10, 2, 5, 1000],
108
+ 'last_4_payments' => [
109
+ { 'amount' => 100, 'currency' => 'USD' },
110
+ { 'amount' => 200, 'currency' => 'USD' },
111
+ { 'amount' => 300, 'currency' => 'USD' },
112
+ { 'amount' => 400, 'currency' => 'USD' }
113
+ ],
114
+ 'array' => [
115
+ [1,2,3],
116
+ [4,5,6],
117
+ [
118
+ 7,
119
+ 8,
120
+ [':D']
121
+ ],
122
+ ]
123
+ }.extend(LightMapper).mapping(
124
+ 'source.google.search_word' => :word,
125
+ 'user.email' => :email,
126
+ 'user.as_json.name' => :name,
127
+ 'roles.0' => :first_role,
128
+ ['roles', 1] => :middle_role,
129
+ 'roles.last' => :last_role,
130
+ (->(source) { source[:mixed][:users].find { |user| user.manager }.email }) => :manager_email,
131
+ (->(source) { source[:mixed][:users].find { |user| user.manager }.name }) => :manager_name,
132
+ 'mixed.users.last.name' => :last_user_name,
133
+ (->(source) { source[:last_4_payments].map(&:values).map(&:first).max }) => :quarterly_payment_amount,
134
+ 'scores.sum' => :final_score,
135
+ 'array.2.2.first' => :smile
136
+ )
137
+ ```
138
+
139
+ result will be:
140
+
141
+ ```ruby
142
+ {
143
+ word: 'ruby',
144
+ email: 'pawel@example.com',
145
+ name: 'Pawel',
146
+ first_role: 'admin',
147
+ last_role: 'user',
148
+ manager_email: 'max@example.com',
149
+ manager_name: 'Max',
150
+ last_user_name: 'Pawel',
151
+ quarterly_payment_amount: 1000,
152
+ final_score: 1017
153
+ }
154
+ ```
155
+
156
+ ### Mappers selection via pattern matching
157
+
158
+ ```ruby
159
+ GOOGLE_MAPPER = { 'result.user.name' => :name }
160
+ LINKEDIN_MAPPER = { 'result.client.display_name' => :word }
161
+
162
+ data = { source: 'google', user: { name: 'test'}, 'result' => { 'user' => { 'name' => 'Pawel'} } }
163
+ mapper = case data
164
+ in source: 'google', user: {name:} then GOOGLE_MAPPER
165
+ in source: 'linkedin', client: {display_name:} then LINKEDIN_MAPPER
166
+ else
167
+ raise 'Unknown mapper'
168
+ end
169
+
170
+ data.extend(LightMapper).mapping(mapper)
171
+ # result { name: 'Pawel' }
172
+ ```
173
+
89
174
  ## Contributing
90
175
 
91
176
  1. Fork it ( https://github.com/[my-github-username]/light_mapper/fork )
data/Rakefile CHANGED
@@ -1,2 +1,6 @@
1
1
  require 'bundler/gem_tasks'
2
+ require 'rspec/core/rake_task'
2
3
 
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task default: :spec
@@ -1,3 +1,3 @@
1
1
  module LightMapper
2
- VERSION = '0.0.2'
2
+ VERSION = '1.0.4'
3
3
  end
data/lib/light_mapper.rb CHANGED
@@ -1,29 +1,78 @@
1
1
  require 'light_mapper/version'
2
2
 
3
3
  module LightMapper
4
- def mapping(mappings, opts = {})
5
- LightMapper.mapping(clone, mappings, opts)
6
- end
4
+ InvalidKey = Class.new(StandardError)
5
+ KeyMissing = Class.new(StandardError)
7
6
 
8
- def self.mapping(hash, mappings, opts = {})
9
- require_keys = opts[:require_keys] == true
10
- any_keys_kind = opts[:any_keys_kind] == true
11
- fetch_method = if any_keys_kind
12
- if require_keys
13
- -> (hash, key) { hash.fetch(key.to_s, hash.fetch(key.to_sym)) }
7
+ module Helper
8
+ def self.raise_key_missing(current, full_path, additional_message = nil)
9
+
10
+ raise KeyMissing, ["#{current} key not found; Full path #{full_path.map(&:to_s).join('.')}", additional_message].compact.join('; ')
11
+ end
12
+
13
+ def self.key_destructor(value)
14
+ case value
15
+ in String
16
+ value.split('.')
17
+ in Symbol
18
+ [value]
19
+ in Array
20
+ value
14
21
  else
15
- -> (hash, key) { hash[key.to_s] || hash[key.to_sym] }
22
+ raise InvalidKey, "Invalid key type: #{value.class}"
16
23
  end
17
- else
18
- if require_keys
19
- -> (hash, key) { hash.fetch(key) }
24
+ end
25
+
26
+ def self.value_extractor(object, current, path, full_path, strict = false, any_keys = false)
27
+ result = case object
28
+ in Hash
29
+ hash_key_extractor(object, current, full_path, strict, any_keys)
30
+ in Array
31
+ array_key_extractor(object, current, full_path, strict, any_keys)
32
+ in NilClass
33
+ nil
34
+ else
35
+ method_name = current.to_s.to_sym
36
+ object.respond_to?(method_name) ? object.send(method_name) : !strict ? nil : raise_key_missing(current, full_path)
37
+ end
38
+
39
+ path.compact.empty? ? result : value_extractor(result, path.first, path[1..-1], full_path, strict, any_keys)
40
+ end
41
+
42
+ def self.hash_key_extractor(object, current, full_path, strict, any_keys)
43
+ keys = any_keys ? [current, current.to_s, current.to_s.to_sym] : [current]
44
+ raise_key_missing(current, full_path) if strict && !keys.any? { |k| object.key?(k) }
45
+
46
+ object.values_at(*keys).compact.first
47
+ end
48
+
49
+ def self.array_key_extractor(object, current, full_path, strict, _any_keys)
50
+ index = current.to_s.match(/^(\d)+$/) ? current.to_i : nil
51
+
52
+ if index
53
+ raise_key_missing(current, full_path) if strict && index && object.size < index.next
54
+
55
+ object[index]
20
56
  else
21
- -> (hash, key) { hash[key] }
57
+ method_name = current.to_s.to_sym
58
+ raise_key_missing(current, full_path, "Array do not respond on #{method_name}") if strict && !object.respond_to?(method_name)
59
+
60
+ object.public_send(method_name)
22
61
  end
23
62
  end
63
+ end
64
+
65
+ def mapping(mappings, opts = {})
66
+ LightMapper.mapping(clone, mappings, opts)
67
+ end
68
+
69
+ def self.mapping(hash, mappings, opts = {})
70
+ strict, any_keys = opts.values_at(:strict, :any_keys)
71
+ mappings.each_with_object({}) do |(k, v), h|
72
+ next h[v] = k.call(hash) if k.is_a?(Proc)
24
73
 
25
- {}.tap do |h|
26
- mappings.each { |k, v| h[v] = fetch_method.call(hash, k) }
74
+ key_path = Helper.key_destructor(k)
75
+ h[v] = Helper.value_extractor(hash, key_path.first, key_path[1..-1], key_path, strict, any_keys)
27
76
  end
28
77
  end
29
78
  end
data/light_mapper.gemspec CHANGED
@@ -17,8 +17,9 @@ Gem::Specification.new do |spec|
17
17
  spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
18
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
19
  spec.require_paths = %w(lib)
20
+ spec.required_ruby_version = '>= 2.7'
20
21
 
21
- spec.add_development_dependency 'bundler', '~> 1.7'
22
+ spec.add_development_dependency 'bundler', '~> 2.3'
22
23
  spec.add_development_dependency 'rake', '~> 10.4'
23
24
  spec.add_development_dependency 'rspec', '~> 3.0'
24
25
  spec.add_development_dependency 'guard', '~> 2.12'
@@ -1,4 +1,17 @@
1
1
  require 'spec_helper'
2
+ class User
3
+ attr_accessor :name, :email, :manager
4
+
5
+ def initialize(name:, email:, manager: false)
6
+ @name = name
7
+ @email = email
8
+ @manager = manager
9
+ end
10
+
11
+ def as_json
12
+ { 'name' => name, 'email' => email, 'manager' => manager }
13
+ end
14
+ end
2
15
 
3
16
  describe LightMapper do
4
17
 
@@ -12,7 +25,7 @@ describe LightMapper do
12
25
  }
13
26
  end
14
27
 
15
- let(:maper) do
28
+ let(:mapper) do
16
29
  {
17
30
  'A' => :a,
18
31
  'b' => 'z'
@@ -20,7 +33,7 @@ describe LightMapper do
20
33
  end
21
34
 
22
35
  it 'return correct hash' do
23
- expect(subject.mapping(maper)).to eq(a: original_hash['A'], 'z' => original_hash['b'])
36
+ expect(subject.mapping(mapper)).to eq(a: original_hash['A'], 'z' => original_hash['b'])
24
37
  end
25
38
  end
26
39
 
@@ -32,7 +45,7 @@ describe LightMapper do
32
45
  }
33
46
  end
34
47
 
35
- let(:maper) do
48
+ let(:mapper) do
36
49
  {
37
50
  'A' => :a,
38
51
  'c' => 'z'
@@ -40,11 +53,11 @@ describe LightMapper do
40
53
  end
41
54
 
42
55
  it 'raise KeyError when required key missing' do
43
- expect { subject.mapping(maper, require_keys: true) }.to raise_error(KeyError)
56
+ expect { subject.mapping(mapper, strict: true) }.to raise_error(LightMapper::KeyMissing)
44
57
  end
45
58
  end
46
59
 
47
- describe 'basic data mapping when any keys kind is allowed' do
60
+ describe 'basic data mapping when any keys kind is allowed' do
48
61
  let(:original_hash) do
49
62
  {
50
63
  'A' => 'test',
@@ -54,7 +67,7 @@ describe LightMapper do
54
67
  }
55
68
  end
56
69
 
57
- let(:maper) do
70
+ let(:mapper) do
58
71
  {
59
72
  'A' => :a,
60
73
  'c' => :c,
@@ -63,7 +76,7 @@ describe LightMapper do
63
76
  end
64
77
 
65
78
  it 'return correct hash' do
66
- expect(subject.mapping(maper, any_keys_kind: true)).to eq(
79
+ expect(subject.mapping(mapper, any_keys: true)).to eq(
67
80
  a: original_hash['A'],
68
81
  c: original_hash[:c],
69
82
  'z' => original_hash['b']
@@ -71,7 +84,7 @@ describe LightMapper do
71
84
  end
72
85
 
73
86
  describe 'raise KeyError' do
74
- let(:maper) do
87
+ let(:mapper) do
75
88
  {
76
89
  'A' => :a,
77
90
  'c' => :c,
@@ -80,11 +93,126 @@ describe LightMapper do
80
93
  end
81
94
 
82
95
  it ' when required key missing' do
83
- expect { subject.mapping(maper, require_keys: true, any_keys_kind: true) }.to raise_error(KeyError)
96
+ expect { subject.mapping(mapper, strict: true, any_keys: true) }.to raise_error(LightMapper::KeyMissing)
84
97
  end
85
98
  end
86
- end
99
+ end
100
+
101
+ describe 'more advanced mapping' do
102
+ let(:source) do
103
+ {
104
+ 'source' => { 'google' => { 'search_word' => 'ruby' } },
105
+ 'user' => User.new(email: 'pawel@example.com', name: 'Pawel'),
106
+ 'roles' => %w[admin manager user],
107
+ 'mixed' => { users: [User.new(email: 'max@example.com', name: 'Max', manager: true), User.new(email: 'pawel@example.com', name: 'Pawel', manager: false)] },
108
+ 'scores' => [ 10, 2, 5, 1000],
109
+ 'last_4_payments' => [
110
+ { 'amount' => 100, 'currency' => 'USD' },
111
+ { 'amount' => 200, 'currency' => 'USD' },
112
+ { 'amount' => 300, 'currency' => 'USD' },
113
+ { 'amount' => 400, 'currency' => 'USD' }
114
+ ],
115
+ 'array' => [
116
+ [1,2,3],
117
+ [4,5,6],
118
+ [
119
+ 7,
120
+ 8,
121
+ [':D']
122
+ ],
123
+ ]
124
+ }
125
+ end
126
+
127
+ context 'with nested hash mapping' do
128
+ let(:mapping) { {'source.google.search_word' => :word} }
129
+
130
+ it 'return correct result' do
131
+ expect(source.extend(LightMapper).mapping(mapping)).to eq(word: 'ruby')
132
+ end
133
+ end
134
+
135
+ context 'with nested array mapping' do
136
+ let(:mapping) { {'array.2.2.first' => :result} }
137
+
138
+ it 'return correct result' do
139
+ expect(source.extend(LightMapper).mapping(mapping)).to eq(result: ':D')
140
+ end
141
+ end
142
+
143
+ context 'with nested object mapping' do
144
+ let(:mapping) { {'user.email' => :result} }
87
145
 
88
- it 'nested keys'
89
- it 'value conversion'
146
+ it 'return correct result' do
147
+ expect(source.extend(LightMapper).mapping(mapping)).to eq(result: 'pawel@example.com')
148
+ end
149
+ end
150
+
151
+ context 'with mapping proc' do
152
+ let(:mapping) { { (->(source) { source['last_4_payments'].last['amount'].to_s }) => :result } }
153
+
154
+ it 'return correct result' do
155
+ expect(source.extend(LightMapper).mapping(mapping)).to eq(result: '400')
156
+ end
157
+ end
158
+
159
+ context 'with mix of nested object types' do
160
+ let(:mapping) do
161
+ {
162
+ 'source.google.search_word' => :word,
163
+ 'user.email' => :email,
164
+ 'user.as_json.name' => :name,
165
+ 'roles.0' => :first_role,
166
+ ['roles', 1] => :middle_role,
167
+ 'roles.last' => :last_role,
168
+ (->(source) { source['mixed'][:users].find { |user| user.manager }.email }) => :manager_email,
169
+ (->(source) { source['mixed'][:users].find { |user| user.manager }.name }) => :manager_name,
170
+ 'mixed.users.last.name' => :last_user_name,
171
+ (->(source) { source['last_4_payments'].map(&:values).map(&:first).max }) => :quarterly_payment_amount,
172
+ 'scores.sum' => :final_score,
173
+ 'array.2.2.first' => :smile
174
+ }
175
+ end
176
+
177
+ it 'return correct result' do
178
+ expect(source.extend(LightMapper).mapping(mapping, any_keys: true)).to match({
179
+ word: 'ruby',
180
+ email: 'pawel@example.com',
181
+ name: 'Pawel',
182
+ final_score: 1017,
183
+ first_role: 'admin',
184
+ last_role: 'user',
185
+ manager_email: 'max@example.com',
186
+ manager_name: 'Max',
187
+ last_user_name: 'Pawel',
188
+ middle_role: 'manager',
189
+ quarterly_payment_amount: 400,
190
+ smile: ':D'
191
+ })
192
+ end
193
+
194
+ it 'return correct result base on proper key types' do
195
+ expect(source.extend(LightMapper).mapping(mapping)).to match({
196
+ word: 'ruby',
197
+ email: 'pawel@example.com',
198
+ name: 'Pawel',
199
+ final_score: 1017,
200
+ first_role: 'admin',
201
+ last_role: 'user',
202
+ manager_email: 'max@example.com',
203
+ manager_name: 'Max',
204
+ last_user_name: nil,
205
+ middle_role: 'manager',
206
+ quarterly_payment_amount: 400,
207
+ smile: ':D'
208
+ })
209
+ end
210
+
211
+ it 'raise KeyMissing error' do
212
+ expect { source.extend(LightMapper).mapping({'user.non_existence_method' => :result}, strict: true) }.to raise_error(LightMapper::KeyMissing)
213
+ expect { source.extend(LightMapper).mapping({'last_4_payments.4' => :result}, strict: true) }.to raise_error(LightMapper::KeyMissing)
214
+ expect { source.extend(LightMapper).mapping({'source.google.missing_key' => :result}, strict: true) }.to raise_error(LightMapper::KeyMissing)
215
+ end
216
+ end
217
+ end
90
218
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: light_mapper
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 1.0.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Pawel Niemczyk
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-04-27 00:00:00.000000000 Z
11
+ date: 2022-12-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '1.7'
19
+ version: '2.3'
20
20
  type: :development
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: '1.7'
26
+ version: '2.3'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: rake
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -147,7 +147,7 @@ homepage: https://github.com/pniemczyk/light_mapper
147
147
  licenses:
148
148
  - MIT
149
149
  metadata: {}
150
- post_install_message:
150
+ post_install_message:
151
151
  rdoc_options: []
152
152
  require_paths:
153
153
  - lib
@@ -155,16 +155,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
155
155
  requirements:
156
156
  - - ">="
157
157
  - !ruby/object:Gem::Version
158
- version: '0'
158
+ version: '2.7'
159
159
  required_rubygems_version: !ruby/object:Gem::Requirement
160
160
  requirements:
161
161
  - - ">="
162
162
  - !ruby/object:Gem::Version
163
163
  version: '0'
164
164
  requirements: []
165
- rubyforge_project:
166
- rubygems_version: 2.4.5
167
- signing_key:
165
+ rubygems_version: 3.1.6
166
+ signing_key:
168
167
  specification_version: 4
169
168
  summary: Very light hash mapper
170
169
  test_files: