invoca-utils 0.1.0 → 0.4.0

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: cb2b2213dbcd47d3b84ae70602cae77f8a1d4ed2
4
- data.tar.gz: 62d0da15a4c7e3db0b66eb9a95830386401411eb
2
+ SHA256:
3
+ metadata.gz: 54dcad50905bf9c0d48905b88e563c6418ea4bc852e4f85d4cd892e2b2031096
4
+ data.tar.gz: 592dde85e812312a5fb5ff67d34da1d5a0127f0c2c2f5f60538baab3c63d32ff
5
5
  SHA512:
6
- metadata.gz: 495277e475d3824a203afb2ee8108695928be969e77a821955c7c2517e0cadc84ed573f1a150275664c9b0aeac99dad823f29f1549b8256c0c06ed024552a589
7
- data.tar.gz: f4839c80b49e4cd72cf2f143d8dbb96cbd2a8567ac33e971d0db295f1775335aff770484d5bcecf8d8bb6fe186eeb6f7898602ba09ababe60ea9891aed7dede3
6
+ metadata.gz: 6fac4beb8eef9b3a4ebc3615cb7711c9134e401e34ba26a2d0012b89c7962ca19440b2ceb2e0fafd5aaafcd4576717d10fddd276c4583858ffc725904194464a
7
+ data.tar.gz: c312101bd66d62f9c145610be64a8e75ec931387d4dc33f581885dc5707ff7565e474fd618ddbf569ed9091a591d0f2e0a164b7899ffe311b889c62fbc5a7d37
data/.gitignore CHANGED
@@ -10,10 +10,12 @@ coverage
10
10
  doc/
11
11
  lib/bundler/man
12
12
  pkg
13
+ pkg/
13
14
  rdoc
14
- spec/reports
15
+ test/reports
15
16
  test/tmp
16
17
  test/version_tmp
18
+
17
19
  tmp
18
20
  *.bundle
19
21
  *.so
@@ -22,4 +24,3 @@ tmp
22
24
  mkmf.log
23
25
  .idea/
24
26
  .rubocop-http*
25
- test/reports
@@ -0,0 +1,50 @@
1
+ #!/usr/bin/groovy
2
+ @Library('jenkins-pipeline@v0.4.5')
3
+ import com.invoca.docker.*;
4
+ pipeline {
5
+ agent {
6
+ kubernetes {
7
+ defaultContainer "ruby"
8
+ yamlFile ".jenkins/ruby_build_pod.yml"
9
+ }
10
+ }
11
+
12
+ environment {
13
+ GITHUB_TOKEN = credentials('github_token')
14
+ BUNDLE_GEM__FURY__IO = credentials('gemfury_deploy_token')
15
+ }
16
+
17
+ stages {
18
+ stage('Setup') {
19
+ steps {
20
+ script {
21
+ sh 'bundle install'
22
+ }
23
+ }
24
+ }
25
+ stage('Unit Test') {
26
+ steps {
27
+ script {
28
+ sh 'bundle exec rake'
29
+ }
30
+ }
31
+ post {
32
+ always { junit '*/reports/*.xml' }
33
+ success { updateGitHubStatus('clean-build', 'success', 'Unit tests.') }
34
+ failure { updateGitHubStatus('clean-build', 'failure', 'Unit tests.') }
35
+ }
36
+ }
37
+ }
38
+ }
39
+
40
+ void updateGitHubStatus(String context, String status, String description) {
41
+ gitHubStatus([
42
+ repoSlug: 'Invoca/invoca-utils',
43
+ sha: env.GIT_COMMIT,
44
+ description: description,
45
+ context: context,
46
+ targetURL: env.BUILD_URL,
47
+ token: env.GITHUB_TOKEN,
48
+ status: status
49
+ ])
50
+ }
@@ -0,0 +1,19 @@
1
+ ---
2
+ apiVersion: v1
3
+ kind: Pod
4
+ metadata:
5
+ labels:
6
+ jenkins/invoca-utils: 'true'
7
+ namespace: jenkins
8
+ name: invoca-utils
9
+ spec:
10
+ containers:
11
+ - name: ruby
12
+ image: ruby:2.6.5
13
+ tty: true
14
+ resources:
15
+ requests:
16
+ memory: "100Mi"
17
+ command:
18
+ - cat
19
+
@@ -1 +1 @@
1
- 2.4.2
1
+ 2.6.5
@@ -0,0 +1,28 @@
1
+ # CHANGELOG for `invoca-utils`
2
+
3
+ Inspired by [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
4
+
5
+ Note: This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
+
7
+ ## [0.4.0] - 2020-06-09
8
+ ### Added
9
+ - Added `Invoca::Utils.retry_on_exception`.
10
+ - Added `Invoca::Utils::GuaranteedUTF8String.normalize_all_strings` to normalize
11
+ all strings found in a JSON doc of hashes, arrays, and values.
12
+
13
+ ## [0.3.0] - 2020-04-28
14
+ ### Added
15
+ - Array::* operator changed to use alias_method instead of prepend to prevent infinite recursion when HoboSupport gem is present
16
+ - Enumerable::map_and_find, map_with_index, and map_hash methods ported from HoboSupport
17
+ - Hash::select_hash, map_hash, partition_hash, & and - methods ported from HoboSupport
18
+ - HashWithIndifferentAccess::partition_hash, & and - methods ported from HoboSupport
19
+ - Module::alias_method_chain ported from HoboSupport
20
+
21
+ ## [0.2.0] - 2020-04-27
22
+ ### Added
23
+ - Enumerable::build_hash method ported from HoboSupport
24
+ - Enumerable::* operator ported from HoboSupport
25
+
26
+ [0.4.0]: https://github.com/Invoca/invoca-utils/compare/v0.3.0...v0.4.0
27
+ [0.3.0]: https://github.com/Invoca/invoca-utils/compare/v0.2.0...v0.3.0
28
+ [0.2.0]: https://github.com/Invoca/invoca-utils/compare/v0.1.1...v0.2.0
data/Gemfile CHANGED
@@ -4,13 +4,19 @@ source 'https://rubygems.org'
4
4
 
5
5
  gemspec
6
6
 
7
- group :development do
7
+ source 'https://rubygems.org' do
8
+ gem 'activesupport', '~> 4.2'
9
+ gem 'minitest'
10
+ gem 'minitest-reporters'
11
+ gem 'pry'
8
12
  gem 'rake'
9
- gem 'test-unit', '= 1.2.3'
10
13
  gem 'rr', '=1.1.2'
11
- gem 'shoulda', '= 3.5.0'
12
- gem 'pry'
13
14
  gem 'ruby-prof'
14
- gem 'minitest'
15
+ gem 'shoulda', '= 3.5.0'
16
+ gem 'test-unit', '= 1.2.3'
15
17
  gem 'tzinfo'
16
18
  end
19
+
20
+ source 'https://gem.fury.io/invoca' do
21
+ gem 'test_overrides', '~> 0.13'
22
+ end
@@ -1,30 +1,39 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- invoca-utils (0.1.0)
4
+ invoca-utils (0.4.0)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
8
+ remote: https://gem.fury.io/invoca/
8
9
  specs:
9
- activesupport (5.1.4)
10
- concurrent-ruby (~> 1.0, >= 1.0.2)
10
+ activesupport (4.2.11.3)
11
11
  i18n (~> 0.7)
12
12
  minitest (~> 5.1)
13
+ thread_safe (~> 0.3, >= 0.3.4)
13
14
  tzinfo (~> 1.1)
14
- coderay (1.1.2)
15
- concurrent-ruby (1.0.5)
16
- hoe (3.16.2)
17
- rake (>= 0.8, < 13.0)
18
- i18n (0.9.1)
15
+ ansi (1.5.0)
16
+ builder (3.2.4)
17
+ coderay (1.1.3)
18
+ concurrent-ruby (1.1.6)
19
+ hoe (3.22.1)
20
+ rake (>= 0.8, < 15.0)
21
+ i18n (0.9.5)
19
22
  concurrent-ruby (~> 1.0)
20
- method_source (0.9.0)
21
- minitest (5.11.1)
22
- pry (0.11.3)
23
- coderay (~> 1.1.0)
24
- method_source (~> 0.9.0)
25
- rake (12.3.0)
23
+ method_source (1.0.0)
24
+ minitest (5.14.1)
25
+ minitest-reporters (1.4.2)
26
+ ansi
27
+ builder
28
+ minitest (>= 5.0)
29
+ ruby-progressbar
30
+ pry (0.13.1)
31
+ coderay (~> 1.1)
32
+ method_source (~> 1.0)
33
+ rake (13.0.1)
26
34
  rr (1.1.2)
27
- ruby-prof (0.17.0)
35
+ ruby-prof (1.4.1)
36
+ ruby-progressbar (1.10.1)
28
37
  shoulda (3.5.0)
29
38
  shoulda-context (~> 1.0, >= 1.0.1)
30
39
  shoulda-matchers (>= 1.4.1, < 3.0)
@@ -33,23 +42,27 @@ GEM
33
42
  activesupport (>= 3.0.0)
34
43
  test-unit (1.2.3)
35
44
  hoe (>= 1.5.1)
45
+ test_overrides (0.13.0)
36
46
  thread_safe (0.3.6)
37
- tzinfo (1.2.4)
47
+ tzinfo (1.2.7)
38
48
  thread_safe (~> 0.1)
39
49
 
40
50
  PLATFORMS
41
51
  ruby
42
52
 
43
53
  DEPENDENCIES
54
+ activesupport (~> 4.2)!
44
55
  invoca-utils!
45
- minitest
46
- pry
47
- rake
48
- rr (= 1.1.2)
49
- ruby-prof
50
- shoulda (= 3.5.0)
51
- test-unit (= 1.2.3)
52
- tzinfo
56
+ minitest!
57
+ minitest-reporters!
58
+ pry!
59
+ rake!
60
+ rr (= 1.1.2)!
61
+ ruby-prof!
62
+ shoulda (= 3.5.0)!
63
+ test-unit (= 1.2.3)!
64
+ test_overrides (~> 0.13)!
65
+ tzinfo!
53
66
 
54
67
  BUNDLED WITH
55
68
  1.17.3
data/Rakefile CHANGED
@@ -3,15 +3,10 @@
3
3
 
4
4
  require "bundler/gem_tasks"
5
5
  require 'rake/testtask'
6
-
7
- Rake::TestTask.new(:test) do |t|
8
- t.libs << 'lib'
9
- t.libs << 'test'
10
- t.pattern = 'test/**/*_test.rb'
11
- t.verbose = false
12
- end
6
+ require 'rake_test_warning_false'
13
7
 
14
8
  task default: :test
15
9
 
16
- task :default => 'test'
17
-
10
+ Rake::TestTask.new do |t|
11
+ t.pattern = "test/**/*_test.rb"
12
+ end
@@ -1,4 +1,4 @@
1
- lib = File.expand_path('../lib', __FILE__)
1
+ lib = File.expand_path('lib', __dir__)
2
2
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
3
  require 'invoca/utils/version'
4
4
 
@@ -7,8 +7,7 @@ Gem::Specification.new do |spec|
7
7
  spec.version = Invoca::Utils::VERSION
8
8
  spec.authors = ["Invoca development"]
9
9
  spec.email = ["development@invoca.com"]
10
- spec.summary = %q{A public collection of helpers used in multiple projects}
11
- spec.description = %q{A public collection of helpers used in multiple projects}
10
+ spec.summary = "A public collection of helpers used in multiple projects"
12
11
  spec.homepage = ""
13
12
  spec.license = "MIT"
14
13
 
@@ -1,19 +1,25 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ module Invoca
4
+ module Utils
5
+ end
6
+ end
7
+
8
+ require "invoca/utils/module"
9
+ require "invoca/utils/array"
10
+ require "invoca/utils/enumerable"
3
11
  require "invoca/utils/diff"
12
+ require "invoca/utils/hash"
13
+ require "invoca/utils/hash_with_indifferent_access"
4
14
  require "invoca/utils/http"
5
15
  require "invoca/utils/map_compact"
6
16
  require "invoca/utils/min_max"
7
17
  require "invoca/utils/stable_sort"
8
18
  require "invoca/utils/time"
19
+ require "invoca/utils/exceptions"
9
20
  require "invoca/utils/guaranteed_utf8_string"
10
21
  require "invoca/utils/version"
11
22
 
12
- module Invoca
13
- module Utils
14
- end
15
- end
16
-
17
23
  unless defined?(Diff)
18
24
  Diff = Invoca::Utils::Diff
19
25
  end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative './multi_sender'
4
+
5
+ # Invoca ::Array extensions
6
+ # TODO: Once the hobo_support gem is no longer used by any of our code, use prepend instead of alias
7
+ class Array
8
+
9
+ alias_method :original_multiply_operator, :* # rubocop:disable Style/Alias
10
+
11
+ def *(rhs = nil)
12
+ if rhs
13
+ original_multiply_operator(rhs)
14
+ else
15
+ Invoca::Utils::MultiSender.new(self, :map)
16
+ end
17
+ end
18
+ end
@@ -88,11 +88,11 @@ module Invoca
88
88
  curr_diff = summary.shift
89
89
  end
90
90
  unless curr_diff && (curr_diff[1].first..curr_diff[1].last) === index
91
- result << " #{format arg}\n" unless arg.nil? || options[:short_description]
91
+ result += " #{format arg}\n" unless arg.nil? || options[:short_description]
92
92
  end
93
93
  if curr_diff && curr_diff[1].first == index
94
94
  verb, _a_range, _b_range, del, add = curr_diff
95
- result <<
95
+ result +=
96
96
  case verb
97
97
  when 'd'
98
98
  del.map { |t| "- #{format t}\n" }.join
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative './multi_sender'
4
+
5
+ # Invoca ::Enumerable extensions
6
+ module Enumerable
7
+ def map_and_find(not_found = nil)
8
+ each do |x|
9
+ val = yield(x)
10
+ return val if val
11
+ end
12
+ not_found
13
+ end
14
+
15
+ def map_with_index(res = [])
16
+ each_with_index do |x, i|
17
+ res << yield(x, i)
18
+ end
19
+ res
20
+ end
21
+
22
+ def build_hash(res = {})
23
+ each do |x|
24
+ pair = block_given? ? yield(x) : x
25
+ res[pair.first] = pair.last if pair
26
+ end
27
+ res
28
+ end
29
+
30
+ def map_hash(res = {})
31
+ each do |x|
32
+ v = yield x
33
+ res[x] = v
34
+ end
35
+ res
36
+ end
37
+
38
+ def *
39
+ Invoca::Utils::MultiSender.new(self, :map)
40
+ end
41
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Invoca
4
+ module Utils
5
+ class << self
6
+ # Yields and rescues any exceptions given in `exception_classes`, retrying the given number of times.
7
+ # The final retry does not rescue any exceptions.
8
+ # The try number (0..retries) is yielded as a block param.
9
+ #
10
+ # @param [Class or Array(Class)] exception_classes - exception()s) to rescue
11
+ # @param [Integer] retries: - 1+ count of retries (1 retry = up to 2 tries total)
12
+ # @param [Proc] before_retry - optional proc which is called before each retry, with the exception passed as a block param
13
+ # @return the value from yield
14
+ def retry_on_exception(exception_classes, retries: 1, before_retry: nil)
15
+ retries.times do |attempt_number|
16
+ return yield(attempt_number)
17
+ rescue *Array(exception_classes) => ex
18
+ before_retry&.call(ex)
19
+ end
20
+
21
+ yield(retries) # no rescue for this last try, so any exceptions will raise out
22
+ end
23
+ end
24
+ end
25
+ end
@@ -21,6 +21,7 @@ module Invoca
21
21
  REPLACE_CHARACTER = '~'
22
22
 
23
23
  class << self
24
+ # normalizes a string to UTF-8
24
25
  def normalize_string(orig_string,
25
26
  normalize_utf16: true,
26
27
  normalize_cp1252: true,
@@ -43,6 +44,22 @@ module Invoca
43
44
  replace_unicode_beyond_ffff: replace_unicode_beyond_ffff)
44
45
  end
45
46
 
47
+ # Walks a JSON doc of hashes, arrays, and values and normalizes all strings found to UTF-8
48
+ def normalize_all_strings(value, **options)
49
+ case value
50
+ when Hash
51
+ value.each_with_object({}) do |(k, v), result|
52
+ result[normalize_all_strings(k, **options)] = normalize_all_strings(v, **options)
53
+ end
54
+ when Array
55
+ value.map { |v| normalize_all_strings(v, **options) }
56
+ when String
57
+ normalize_string(value, **options)
58
+ else
59
+ value
60
+ end
61
+ end
62
+
46
63
  private
47
64
 
48
65
  def normalize_string_from_utf8(string,
@@ -54,13 +71,11 @@ module Invoca
54
71
  found_utf_16 = normalize_utf_16(string, normalize_cp1252: normalize_cp1252) if normalize_utf16
55
72
  if found_utf_16
56
73
  string.encode!('UTF-8')
57
- else
58
- unless string.valid_encoding?
59
- if normalize_cp1252
60
- cp1252_to_utf_8(string)
61
- else
62
- raise ArgumentError, 'Could not normalize to utf8 due to invalid characters (probably CP1252)'
63
- end
74
+ elsif !string.valid_encoding?
75
+ if normalize_cp1252
76
+ cp1252_to_utf_8(string)
77
+ else
78
+ raise ArgumentError, 'Could not normalize to utf8 due to invalid characters (probably CP1252)'
64
79
  end
65
80
  end
66
81
  normalize_newlines(string) if normalize_newlines
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Invoca ::Hash extensions
4
+ class Hash
5
+ def select_hash(&block)
6
+ res = {}
7
+ each { |k, v| res[k] = v if (block.arity == 1 ? yield(v) : yield(k, v)) } # rubocop:disable Style/ParenthesesAroundCondition
8
+ res
9
+ end
10
+
11
+ def map_hash(&block)
12
+ res = {}
13
+ each { |k, v| res[k] = block.arity == 1 ? yield(v) : yield(k, v) }
14
+ res
15
+ end
16
+
17
+ def partition_hash(keys = nil)
18
+ yes = {}
19
+ no = {}
20
+ each do |k, v|
21
+ if block_given? ? yield(k, v) : keys.include?(k)
22
+ yes[k] = v
23
+ else
24
+ no[k] = v
25
+ end
26
+ end
27
+ [yes, no]
28
+ end
29
+
30
+ # rubocop:disable Naming/BinaryOperatorParameterName
31
+ def -(keys)
32
+ res = {}
33
+ each_pair { |k, v| res[k] = v unless k.in?(keys) }
34
+ res
35
+ end
36
+
37
+ def &(keys)
38
+ res = {}
39
+ keys.each { |k| res[k] = self[k] if has_key?(k) }
40
+ res
41
+ end
42
+ # rubocop:enable Naming/BinaryOperatorParameterName
43
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Invoca ::HashWithIndifferentAccess extensions
4
+ if defined? HashWithIndifferentAccess
5
+
6
+ class HashWithIndifferentAccess
7
+
8
+ # rubocop:disable Naming/BinaryOperatorParameterName
9
+ def -(keys)
10
+ res = HashWithIndifferentAccess.new
11
+ keys = keys.map { |k| k.is_a?(Symbol) ? k.to_s : k }
12
+ each_pair { |k, v| res[k] = v unless k.in?(keys) }
13
+ res
14
+ end
15
+
16
+ def &(keys)
17
+ res = HashWithIndifferentAccess.new
18
+ keys.each do |k|
19
+ k = k.to_s if k.is_a?(Symbol)
20
+ res[k] = self[k] if has_key?(k)
21
+ end
22
+ res
23
+ end
24
+ # rubocop:enable Naming/BinaryOperatorParameterName
25
+
26
+ def partition_hash(keys = nil)
27
+ keys = keys&.map { |k| k.is_a?(Symbol) ? k.to_s : k }
28
+ yes = HashWithIndifferentAccess.new
29
+ no = HashWithIndifferentAccess.new
30
+ each do |k, v|
31
+ if block_given? ? yield(k, v) : keys.include?(k)
32
+ yes[k] = v
33
+ else
34
+ no[k] = v
35
+ end
36
+ end
37
+ [yes, no]
38
+ end
39
+
40
+ end
41
+
42
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Module
4
+
5
+ # Custom alias_method_chain that won't cause infinite recursion if called twice.
6
+ # NOTE: Calling alias_method_chain on alias_method_chain was just way too confusing, so I copied it :-/
7
+ def alias_method_chain(target, feature)
8
+ # Strip out punctuation on predicates, bang or writer methods since
9
+ # e.g. target?_without_feature is not a valid method name.
10
+ aliased_target, punctuation = target.to_s.sub(/([?!=])$/, ''), $1 # rubocop:disable Style/PerlBackrefs
11
+ yield(aliased_target, punctuation) if block_given?
12
+
13
+ with_method = "#{aliased_target}_with_#{feature}#{punctuation}"
14
+ without_method = "#{aliased_target}_without_#{feature}#{punctuation}"
15
+
16
+ unless method_defined?(without_method)
17
+ alias_method without_method, target
18
+ alias_method target, with_method
19
+
20
+ if public_method_defined?(without_method)
21
+ public target
22
+ elsif protected_method_defined?(without_method)
23
+ protected target
24
+ elsif private_method_defined?(without_method)
25
+ private target
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Invoca
4
+ module Utils
5
+ class MultiSender
6
+ undef_method(*(instance_methods - [:__id__, :__send__, :object_id]))
7
+
8
+ def initialize(enumerable, method)
9
+ @enumerable = enumerable
10
+ @method = method
11
+ end
12
+
13
+ # rubocop:disable Style/MethodMissingSuper
14
+ # rubocop:disable Style/MissingRespondToMissing
15
+ def method_missing(name, *args, &block)
16
+ @enumerable.send(@method) { |x| x.send(name, *args, &block) }
17
+ end
18
+ # rubocop:enable Style/MethodMissingSuper
19
+ # rubocop:enable Style/MissingRespondToMissing
20
+ end
21
+ end
22
+ end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Invoca
4
4
  module Utils
5
- VERSION = "0.1.0"
5
+ VERSION = "0.4.0"
6
6
  end
7
7
  end
@@ -7,3 +7,8 @@ require 'pry'
7
7
  require 'active_support/all'
8
8
 
9
9
  require 'invoca/utils'
10
+ require "minitest/reporters"
11
+ Minitest::Reporters.use! [
12
+ Minitest::Reporters::DefaultReporter.new,
13
+ Minitest::Reporters::JUnitReporter.new
14
+ ]
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../lib/invoca/utils/array.rb'
4
+ require_relative '../test_helper'
5
+
6
+ class ArrayTest < Minitest::Test
7
+ context '* operator' do
8
+ should 'call the same method on each item in an array and return the results as an array' do
9
+ assert_equal([4, 5, 5], ['some', 'short', 'words'].*.length)
10
+ end
11
+
12
+ should 'handle methods with arguments' do
13
+ assert_equal(['om', 'ho', 'or'], ['some', 'short', 'words'].*.slice(1, 2))
14
+ end
15
+
16
+ should 'not alter normal behavior (multiplication) when there is a right hand side to the expression' do
17
+ assert_equal(['some', 'short', 'words', 'some', 'short', 'words'], ['some', 'short', 'words'] * 2)
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,80 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'set'
4
+ require_relative '../../lib/invoca/utils/enumerable.rb'
5
+ require_relative '../test_helper'
6
+
7
+ class EnumerableTest < Minitest::Test
8
+
9
+ context 'map_and_find' do
10
+ should 'return the mapped value of the first match' do
11
+ assert_equal('FOUND 3', [1, 2, 3, 4].map_and_find { |v| 'FOUND 3' if v == 3 })
12
+ end
13
+
14
+ should 'return the mapped value of the first match, even if there are multiple matches' do
15
+ assert_equal('FOUND 3', [1, 2, 3, 4].map_and_find { |v| "FOUND #{v}" if v > 2 })
16
+ end
17
+
18
+ should 'return the provided argument if the value is not found' do
19
+ assert_equal('NOT FOUND', [1, 2, 3, 4].map_and_find('NOT FOUND') { |v| "FOUND 6" if v == 6 })
20
+ end
21
+
22
+ should 'return nil if the value is not found and no argument is provided' do
23
+ assert_nil([1, 2, 3, 4].map_and_find { |v| "FOUND 6" if v == 6 })
24
+ end
25
+ end
26
+
27
+ context 'map_with_index' do
28
+ should 'call the block with the value and index' do
29
+ assert_equal([10, 21, 32, 43], [10, 20, 30, 40].map_with_index { |v, index| v + index })
30
+ end
31
+
32
+ should 'assumulate into the provided enumerable' do
33
+ assert_equal([1, 10, 21, 32, 43], [10, 20, 30, 40].map_with_index([1]) { |v, index| v + index })
34
+ end
35
+ end
36
+
37
+ context 'map_hash' do
38
+ should 'convert enumerables into a hash using the value for key and the map result as the hash value' do
39
+ assert_equal({ 1 => 11, 2 => 12, 3 => 13 }, [1, 2, 3].map_hash { |v| v + 10 })
40
+ end
41
+
42
+ should 'includes nils returned from map' do
43
+ assert_equal({ 1 => 11, 2 => nil, 3 => 13 }, [1, 2, 3].map_hash { |v| v + 10 unless v == 2 })
44
+ end
45
+ end
46
+
47
+ context 'build_hash' do
48
+ should 'convert arrays of [key, value] to a hash of { key => value }' do
49
+ assert_equal({ 'some' => 4, 'short' => 5, 'words' => 5 }, ['some', 'short', 'words'].build_hash { |s| [s, s.length] })
50
+ end
51
+
52
+ should 'ignore nils returned from map' do
53
+ assert_equal({ 'some' => 4, 'words' => 5 }, ['some', 'short', 'words'].build_hash { |s| s == 'short' ? nil : [s, s.length] })
54
+ end
55
+
56
+ # these seem like erroneous behavior, but, they have been left as-is for backward compatibility with hobosupport::Enumerable::build_hash
57
+
58
+ should 'convert arrays of [single_value] to a hash of { single_value => single_value }' do
59
+ assert_equal({ 'some' => 4, 'short' => 'short', 'words' => 5 }, ['some', 'short', 'words'].build_hash { |s| s == 'short' ? [s] : [s, s.length] })
60
+ end
61
+
62
+ should 'convert arrays of [first, ..., last] to a hash of { first => last }' do
63
+ assert_equal({ 'some' => 4, 'short' => 'three', 'words' => 5 }, ['some', 'short', 'words'].build_hash { |s| s == 'short' ? [s, 'two', 'three'] : [s, s.length] })
64
+ end
65
+
66
+ should 'convert empty arrays to a hash of { nil => nil }' do
67
+ assert_equal({ 'some' => 4, nil => nil, 'words' => 5 }, ['some', 'short', 'words'].build_hash { |s| s == 'short' ? [] : [s, s.length] })
68
+ end
69
+ end
70
+
71
+ context '* operator' do
72
+ should 'call the same method on each item in an Set and return the results as an array' do
73
+ assert_equal([4, 5, 5], Set['some', 'short', 'words'].*.length)
74
+ end
75
+
76
+ should 'call the same method on each item in an Hash and return the results as an array' do
77
+ assert_equal(['key1:value1', 'key2:value2'], { key1: 'value1', key2: 'value2' }.*.join(':'))
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../test_helper'
4
+
5
+ class ExceptionsTest < Minitest::Test
6
+ context "exceptions" do
7
+ context ".retry_on_exception" do
8
+ should "default retries: to 1" do
9
+ times = 0
10
+ tries = []
11
+ result = Invoca::Utils.retry_on_exception(ArgumentError) do |try|
12
+ tries << try
13
+ times += 1
14
+ end
15
+ assert_equal 1, result
16
+ assert_equal [0], tries
17
+ end
18
+
19
+ context "when never raising an exception" do
20
+ should "return result" do
21
+ times = 0
22
+ tries = []
23
+ result = Invoca::Utils.retry_on_exception(ArgumentError, retries: 2) do |try|
24
+ tries << try
25
+ times += 1
26
+ try == 0 and raise ArgumentError, '!!!'
27
+ times
28
+ end
29
+ assert_equal 2, result
30
+ assert_equal [0, 1], tries
31
+ end
32
+ end
33
+
34
+ context "when always raising an exception" do
35
+ should "retry and finally raise" do
36
+ tries = []
37
+ assert_raises(ArgumentError, /!!! 2/) do
38
+ Invoca::Utils.retry_on_exception(ArgumentError, retries: 1) do |try|
39
+ tries << try
40
+ raise ArgumentError, "!!! #{try + 1}"
41
+ end
42
+ end
43
+ assert_equal [0, 1], tries
44
+ end
45
+ end
46
+
47
+ context "when raising but then succeeding" do
48
+ should "retry and finally return result" do
49
+ times = 0
50
+ result = Invoca::Utils.retry_on_exception(ArgumentError, retries: 1) do
51
+ times += 1
52
+ if times == 1
53
+ raise ArgumentError, "!!! #{times}"
54
+ else
55
+ times
56
+ end
57
+ end
58
+ assert_equal 2, result
59
+ end
60
+ end
61
+
62
+ context "when raising different exceptions (array notation) but then succeeding" do
63
+ should "retry and finally return result" do
64
+ times = 0
65
+ tries = []
66
+ result = Invoca::Utils.retry_on_exception([ArgumentError, RuntimeError], retries: 2) do |try|
67
+ tries << try
68
+ times += 1
69
+ case times
70
+ when 1
71
+ raise ArgumentError, "!!! #{times}"
72
+ when 2
73
+ raise RuntimeError, "!!! #{times}"
74
+ else
75
+ times
76
+ end
77
+ end
78
+ assert_equal 3, result
79
+ assert_equal [0, 1, 2], tries
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end
@@ -221,6 +221,29 @@ class GuaranteedUTF8StringTest < Minitest::Test
221
221
  assert_equal Encoding::UTF_8, encoded_string.encoding
222
222
  end
223
223
  end
224
+
225
+ context ".normalize_strings" do
226
+ should "walk json doc, replacing strings in: values, inside array elements, and hash keys and values" do
227
+ json_doc = {
228
+ '😹' => "\xE2\x9C\x93 laughing cat",
229
+ ['😹'] => ["\xE2", "\xf0\x9f\x98\xb9", { "newline" => "\r\n" }],
230
+ 'cp1252' => "\x91smart quotes\x92"
231
+ }
232
+
233
+ normalized_json = Invoca::Utils::GuaranteedUTF8String.normalize_all_strings(json_doc,
234
+ normalize_utf16: true,
235
+ normalize_cp1252: true,
236
+ normalize_newlines: true,
237
+ remove_utf8_bom: true,
238
+ replace_unicode_beyond_ffff: true)
239
+
240
+ assert_equal({
241
+ '~' => "✓ laughing cat",
242
+ ['~'] => ["â", "~", { "newline" => "\n" }],
243
+ 'cp1252' => "‘smart quotes’"
244
+ }, normalized_json)
245
+ end
246
+ end
224
247
  end
225
248
 
226
249
  context 'constructor' do
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../lib/invoca/utils/hash.rb'
4
+ require_relative '../test_helper'
5
+
6
+ class HashTest < Minitest::Test
7
+
8
+ context 'select_hash' do
9
+ should 'return a hash containing key/values identified by the block' do
10
+ assert_equal({ 1 => 2, 3 => 4 }, { 1 => 2, 3 => 4, 6 => 5 }.select_hash { |key, value| key < value })
11
+ end
12
+
13
+ should 'handle blocks that only check values' do
14
+ assert_equal({ 3 => 4, 6 => 5 }, { 1 => 2, 3 => 4, 6 => 5 }.select_hash { |value| value != 2 })
15
+ end
16
+ end
17
+
18
+ context 'map_hash' do
19
+ should 'return a hash containing values updated by the block' do
20
+ assert_equal({ 1 => true, 3 => true, 6 => false }, { 1 => 2, 3 => 4, 6 => 5 }.map_hash { |key, value| key < value })
21
+ end
22
+
23
+ should 'handle blocks that only receive values' do
24
+ assert_equal({ 1 => 4, 3 => 8, 6 => 10 }, { 1 => 2, 3 => 4, 6 => 5 }.map_hash { |value| value * 2 })
25
+ end
26
+ end
27
+
28
+ context 'partition_hash' do
29
+ should 'return two hashes, the first contains the pairs with matching keys, the second contains the rest' do
30
+ assert_equal([{ 1 => 2, 3 => 4 }, { 6 => 5 }], { 1 => 2, 3 => 4, 6 => 5 }.partition_hash([1, 3]))
31
+ end
32
+
33
+ should 'return two hashes, the first contains the pairs with identified by the block, the second contains the rest' do
34
+ assert_equal([{ 1 => 2, 3 => 4 }, { 6 => 5 }], { 1 => 2, 3 => 4, 6 => 5 }.partition_hash { |key, value| key < value })
35
+ end
36
+
37
+ should 'handle no matches' do
38
+ assert_equal([{}, { 1 => 2, 3 => 4, 6 => 5 }], { 1 => 2, 3 => 4, 6 => 5 }.partition_hash([100]))
39
+ end
40
+
41
+ should 'handle all matches' do
42
+ assert_equal([{ 1 => 2, 3 => 4, 6 => 5 }, {}], { 1 => 2, 3 => 4, 6 => 5 }.partition_hash { |_key, _value| true })
43
+ end
44
+ end
45
+
46
+ context '- operator' do
47
+ should 'return a hash with pairs removed that match the keys in rhs array' do
48
+ assert_equal({ 3 => 4 }, { 1 => 2, 3 => 4, 6 => 5 } - [1, 6])
49
+ end
50
+
51
+ should 'handle empty rhs array' do
52
+ assert_equal({ 1 => 2, 3 => 4, 6 => 5 }, { 1 => 2, 3 => 4, 6 => 5 } - [])
53
+ end
54
+
55
+ should 'handle no matches in rhs array' do
56
+ assert_equal({ 1 => 2, 3 => 4, 6 => 5 }, { 1 => 2, 3 => 4, 6 => 5 } - [100, 600])
57
+ end
58
+
59
+ should 'handle all matches in rhs array' do
60
+ assert_equal({}, { 1 => 2, 3 => 4, 6 => 5 } - [1, 3, 6])
61
+ end
62
+ end
63
+
64
+ context '& operator' do
65
+ should 'return a hash with pairs removed that do NOT match the keys in rhs array' do
66
+ assert_equal({ 1 => 2, 6 => 5 }, { 1 => 2, 3 => 4, 6 => 5 } & [1, 6])
67
+ end
68
+
69
+ should 'handle empty rhs array' do
70
+ assert_equal({}, { 1 => 2, 3 => 4, 6 => 5 } & [])
71
+ end
72
+
73
+ should 'handle no matches in rhs array' do
74
+ assert_equal({}, { 1 => 2, 3 => 4, 6 => 5 } & [100, 600])
75
+ end
76
+
77
+ should 'handle all matches in rhs array' do
78
+ assert_equal({ 1 => 2, 3 => 4, 6 => 5 }, { 1 => 2, 3 => 4, 6 => 5 } & [1, 3, 6])
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,100 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support/hash_with_indifferent_access'
4
+ require_relative '../../lib/invoca/utils/hash_with_indifferent_access.rb'
5
+ require_relative '../test_helper'
6
+
7
+ class HashWithIndifferentAccessTest < Minitest::Test
8
+
9
+ context 'partition_hash' do
10
+ setup do
11
+ @hash_to_test = HashWithIndifferentAccess.new('one' => 2, :three => 4, 'six' => 5)
12
+ end
13
+
14
+ should 'return two hashes, the first contains the pairs with matching keys, the second contains the rest' do
15
+ assert_equal([{ 'one' => 2, 'three' => 4 }, { 'six' => 5 }], @hash_to_test.partition_hash(['one', 'three']))
16
+ end
17
+
18
+ should 'return two hashes, the first contains the pairs with identified by the block, the second contains the rest' do
19
+ assert_equal([{ 'one' => 2, 'three' => 4 }, { 'six' => 5 }], @hash_to_test.partition_hash { |key, _value| ['one', 'three'].include?(key) })
20
+ end
21
+
22
+ should 'handle no matches' do
23
+ assert_equal([{}, @hash_to_test], @hash_to_test.partition_hash([:not_found]))
24
+ end
25
+
26
+ should 'handle all matches' do
27
+ assert_equal([@hash_to_test, {}], @hash_to_test.partition_hash { |_key, _value| true })
28
+ end
29
+
30
+ should 'handle symbols for key matching' do
31
+ assert_equal([{ 'one' => 2, 'three' => 4 }, { 'six' => 5 }], @hash_to_test.partition_hash([:one, :three]))
32
+ end
33
+
34
+ should 'return HashWithIndifferentAccess objects' do
35
+ matched, unmatched = @hash_to_test.partition_hash([:one, :three])
36
+ assert(matched.is_a?(HashWithIndifferentAccess))
37
+ assert(unmatched.is_a?(HashWithIndifferentAccess))
38
+ end
39
+ end
40
+
41
+ context '- operator' do
42
+ setup do
43
+ @hash_to_test = HashWithIndifferentAccess.new('one' => 2, :three => 4, 'six' => 5)
44
+ end
45
+
46
+ should 'return a hash with pairs removed that match the keys in rhs array' do
47
+ assert_equal({ 'three' => 4 }, @hash_to_test - ['one', 'six'])
48
+ end
49
+
50
+ should 'handle empty rhs array' do
51
+ assert_equal(@hash_to_test, @hash_to_test - [])
52
+ end
53
+
54
+ should 'handle no matches in rhs array' do
55
+ assert_equal(@hash_to_test, @hash_to_test - ['100', '600'])
56
+ end
57
+
58
+ should 'handle all matches in rhs array' do
59
+ assert_equal({}, @hash_to_test - ['one', 'three', 'six'])
60
+ end
61
+
62
+ should 'handle symbols for key matching' do
63
+ assert_equal({ 'six' => 5 }, @hash_to_test - [:one, :three])
64
+ end
65
+
66
+ should 'return HashWithIndifferentAccess object' do
67
+ assert((@hash_to_test - [:one, :three]).is_a?(HashWithIndifferentAccess))
68
+ end
69
+ end
70
+
71
+ context '& operator' do
72
+ setup do
73
+ @hash_to_test = HashWithIndifferentAccess.new('one' => 2, :three => 4, 'six' => 5)
74
+ end
75
+
76
+ should 'return a hash with pairs removed that do NOT match the keys in rhs array' do
77
+ assert_equal({ 'one' => 2, 'six' => 5 }, @hash_to_test & ['one', 'six'])
78
+ end
79
+
80
+ should 'handle empty rhs array' do
81
+ assert_equal({}, @hash_to_test & [])
82
+ end
83
+
84
+ should 'handle no matches in rhs array' do
85
+ assert_equal({}, @hash_to_test & ['100', '600'])
86
+ end
87
+
88
+ should 'handle all matches in rhs array' do
89
+ assert_equal(@hash_to_test, @hash_to_test & ['one', 'three', 'six'])
90
+ end
91
+
92
+ should 'handle symbols for key matching' do
93
+ assert_equal({ 'one' => 2, 'three' => 4 }, @hash_to_test & [:one, :three])
94
+ end
95
+
96
+ should 'return HashWithIndifferentAccess object' do
97
+ assert((@hash_to_test & [:one, :three]).is_a?(HashWithIndifferentAccess))
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ # must require active_support's alias_method_chain first, to ensure that our module monkey patches it
4
+ require 'active_support/core_ext/module/aliasing'
5
+ require_relative '../../lib/invoca/utils/module.rb'
6
+ require_relative '../test_helper'
7
+
8
+ class ModuleTest < Minitest::Test
9
+ class NumberFun
10
+ def self.around_filter(around_method, method_names)
11
+ method_names.each do |meth|
12
+ define_method("#{meth}_with_around_filter") do |*args|
13
+ send(around_method, *args) do |*ar_args|
14
+ send("#{meth}_without_around_filter", *ar_args)
15
+ end
16
+ end
17
+
18
+ alias_method_chain meth, :around_filter
19
+ end
20
+ end
21
+
22
+ def increment_filter(num)
23
+ yield(num + 1)
24
+ end
25
+
26
+ def number_printer(num)
27
+ num
28
+ end
29
+
30
+ around_filter :increment_filter, [:number_printer]
31
+ around_filter :increment_filter, [:number_printer]
32
+ end
33
+
34
+ context 'alias_method_chain' do
35
+ should 'not cause infinite recursion when double aliasing the same method' do
36
+ assert_equal(4, NumberFun.new.number_printer(3))
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../lib/invoca/utils/multi_sender.rb'
4
+ require_relative '../test_helper'
5
+
6
+ class MultiSenderTest < Minitest::Test
7
+ # create enumerable class for testing
8
+ class LinkedList
9
+ include Enumerable
10
+
11
+ def initialize(head, tail = nil)
12
+ @head, @tail = head, tail
13
+ end
14
+
15
+ def <<(item)
16
+ LinkedList.new(item, self)
17
+ end
18
+
19
+ def inspect
20
+ [@head, @tail].inspect
21
+ end
22
+
23
+ def each(&block)
24
+ if block_given?
25
+ block.call(@head)
26
+ @tail&.each(&block)
27
+ else
28
+ to_enum(:each)
29
+ end
30
+ end
31
+ end
32
+
33
+ context 'MultiSender' do
34
+ context 'with custom Enumerable' do
35
+ setup do
36
+ linked_list = LinkedList.new('some') << 'short' << 'words'
37
+ @multi_sender = Invoca::Utils::MultiSender.new(linked_list, :map)
38
+ end
39
+
40
+ should 'call the same method on each item in an Enumerable and return the results as an array' do
41
+ assert_equal([5, 5, 4], @multi_sender.length)
42
+ end
43
+
44
+ should 'handle methods with arguments' do
45
+ assert_equal(['or', 'ho', 'om'], @multi_sender.slice(1, 2))
46
+ end
47
+ end
48
+
49
+ context 'with built-in Array' do
50
+ should 'call the same method on each item in an Array and return the results as an array' do
51
+ multi_sender = Invoca::Utils::MultiSender.new(['some', 'short', 'words'], :map)
52
+ assert_equal([4, 5, 5], multi_sender.length)
53
+ end
54
+ end
55
+ end
56
+ end
metadata CHANGED
@@ -1,16 +1,16 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: invoca-utils
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Invoca development
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-03-13 00:00:00.000000000 Z
11
+ date: 2020-06-09 00:00:00.000000000 Z
12
12
  dependencies: []
13
- description: A public collection of helpers used in multiple projects
13
+ description:
14
14
  email:
15
15
  - development@invoca.com
16
16
  executables: []
@@ -18,8 +18,11 @@ extensions: []
18
18
  extra_rdoc_files: []
19
19
  files:
20
20
  - ".gitignore"
21
+ - ".jenkins/Jenkinsfile"
22
+ - ".jenkins/ruby_build_pod.yml"
21
23
  - ".rubocop.yml"
22
24
  - ".ruby-version"
25
+ - CHANGELOG.md
23
26
  - Gemfile
24
27
  - Gemfile.lock
25
28
  - LICENSE.txt
@@ -27,18 +30,32 @@ files:
27
30
  - Rakefile
28
31
  - invoca-utils.gemspec
29
32
  - lib/invoca/utils.rb
33
+ - lib/invoca/utils/array.rb
30
34
  - lib/invoca/utils/diff.rb
35
+ - lib/invoca/utils/enumerable.rb
36
+ - lib/invoca/utils/exceptions.rb
31
37
  - lib/invoca/utils/guaranteed_utf8_string.rb
38
+ - lib/invoca/utils/hash.rb
39
+ - lib/invoca/utils/hash_with_indifferent_access.rb
32
40
  - lib/invoca/utils/http.rb
33
41
  - lib/invoca/utils/map_compact.rb
34
42
  - lib/invoca/utils/min_max.rb
43
+ - lib/invoca/utils/module.rb
44
+ - lib/invoca/utils/multi_sender.rb
35
45
  - lib/invoca/utils/stable_sort.rb
36
46
  - lib/invoca/utils/time.rb
37
47
  - lib/invoca/utils/version.rb
38
48
  - test/helpers/constant_overrides.rb
39
49
  - test/test_helper.rb
50
+ - test/unit/array_test.rb
51
+ - test/unit/enumerable_test.rb
52
+ - test/unit/exceptions_test.rb
40
53
  - test/unit/guaranteed_utf8_string_test.rb
54
+ - test/unit/hash_test.rb
55
+ - test/unit/hash_with_indifferent_access_test.rb
41
56
  - test/unit/map_compact_test.rb
57
+ - test/unit/module_test.rb
58
+ - test/unit/multi_sender_test.rb
42
59
  - test/unit/stable_sort_test.rb
43
60
  - test/unit/time_calculations_test.rb
44
61
  - test/unit/utils_test.rb
@@ -62,16 +79,22 @@ required_rubygems_version: !ruby/object:Gem::Requirement
62
79
  - !ruby/object:Gem::Version
63
80
  version: '0'
64
81
  requirements: []
65
- rubyforge_project:
66
- rubygems_version: 2.6.13
82
+ rubygems_version: 3.0.3
67
83
  signing_key:
68
84
  specification_version: 4
69
85
  summary: A public collection of helpers used in multiple projects
70
86
  test_files:
71
87
  - test/helpers/constant_overrides.rb
72
88
  - test/test_helper.rb
89
+ - test/unit/array_test.rb
90
+ - test/unit/enumerable_test.rb
91
+ - test/unit/exceptions_test.rb
73
92
  - test/unit/guaranteed_utf8_string_test.rb
93
+ - test/unit/hash_test.rb
94
+ - test/unit/hash_with_indifferent_access_test.rb
74
95
  - test/unit/map_compact_test.rb
96
+ - test/unit/module_test.rb
97
+ - test/unit/multi_sender_test.rb
75
98
  - test/unit/stable_sort_test.rb
76
99
  - test/unit/time_calculations_test.rb
77
100
  - test/unit/utils_test.rb