corruption_monkey 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +32 -0
- data/.ruby-version +1 -0
- data/CONTRIBUTING.md +5 -0
- data/Gemfile +8 -0
- data/Gemfile.lock +21 -0
- data/LICENSE.md +21 -0
- data/README.md +61 -0
- data/Rakefile +9 -0
- data/bin/flip +5 -0
- data/corruption_monkey.gemspec +18 -0
- data/lib/corruption_monkey/cli.rb +67 -0
- data/lib/corruption_monkey/version.rb +3 -0
- data/lib/corruption_monkey.rb +58 -0
- data/test/cli_test.rb +56 -0
- data/test/lib_test.rb +103 -0
- data/test/test_helpers.rb +17 -0
- data/travis.yml +4 -0
- metadata +67 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 4b6874de9bd4a6d99c99d10e6ffa8ff196d0e380
|
4
|
+
data.tar.gz: 74071c21f62bbecb7a1010a0c1facd60eda9ddf9
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 9beb55581474d4f6e9ea3976da3c2566c33f24e63d9d21617613a5905188addd44f7cb9648fe021a6ba1de86c04bca409bb5f4e1f58c05b4af6c690c2ff2ee49
|
7
|
+
data.tar.gz: a8eaa52a59155d0a93aee547888ac985621f144e6233c7ceb14862a53c64fb62f03bc8ef5969428cc05e03d2a8cdea6af7db887ead36183a7fdfe63a0fc41475
|
data/.gitignore
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
# Vim
|
2
|
+
[._]*.s[a-w][a-z]
|
3
|
+
[._]s[a-w][a-z]
|
4
|
+
# session
|
5
|
+
Session.vim
|
6
|
+
.netrwhist
|
7
|
+
*~
|
8
|
+
tags
|
9
|
+
|
10
|
+
# Ruby
|
11
|
+
*.gem
|
12
|
+
*.rbc
|
13
|
+
/.config
|
14
|
+
/coverage/
|
15
|
+
/InstalledFiles
|
16
|
+
/pkg/
|
17
|
+
/spec/reports/
|
18
|
+
/spec/examples.txt
|
19
|
+
/test/tmp/
|
20
|
+
/test/version_tmp/
|
21
|
+
/tmp/
|
22
|
+
|
23
|
+
## Documentation cache and generated files:
|
24
|
+
/.yardoc/
|
25
|
+
/_yardoc/
|
26
|
+
/doc/
|
27
|
+
/rdoc/
|
28
|
+
|
29
|
+
## Environment normalization:
|
30
|
+
/.bundle/
|
31
|
+
/vendor/bundle
|
32
|
+
/lib/bundler/man/
|
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
2.3.1
|
data/CONTRIBUTING.md
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
corruption_monkey (0.1.2)
|
5
|
+
|
6
|
+
GEM
|
7
|
+
remote: https://rubygems.org/
|
8
|
+
specs:
|
9
|
+
minitest (5.9.0)
|
10
|
+
rake (11.2.2)
|
11
|
+
|
12
|
+
PLATFORMS
|
13
|
+
ruby
|
14
|
+
|
15
|
+
DEPENDENCIES
|
16
|
+
corruption_monkey!
|
17
|
+
minitest
|
18
|
+
rake
|
19
|
+
|
20
|
+
BUNDLED WITH
|
21
|
+
1.12.5
|
data/LICENSE.md
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2016 Shopify
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
### Corruption monkey
|
2
|
+
TL;DR: do 🔬 with backups (and 🐒)
|
3
|
+
|
4
|
+
This Ruby library (and CLI) provides an easy way to flip bits in a provided file.
|
5
|
+
|
6
|
+
It was built in order to corrupt database backups and test if our current and future tools work and calculate their coverage.
|
7
|
+
|
8
|
+
#### Local installation
|
9
|
+
```bash
|
10
|
+
$ gem install corruption_monkey
|
11
|
+
```
|
12
|
+
or
|
13
|
+
```bash
|
14
|
+
$ git clone https://github.com/Shopify/corruption_monkey && cd corruption_monkey
|
15
|
+
$ rake install
|
16
|
+
```
|
17
|
+
|
18
|
+
#### CLI
|
19
|
+
```bash
|
20
|
+
# flip a bit in a specific bit index
|
21
|
+
$ flip <file> <position>
|
22
|
+
|
23
|
+
# flip a bit in some part of the file
|
24
|
+
$ flip <file> <percent>%
|
25
|
+
|
26
|
+
# flip a bit... somewhere ¯\_(ツ)_/¯
|
27
|
+
$ flip <file> chaos
|
28
|
+
|
29
|
+
# show help message with this examples
|
30
|
+
$ flip [help]
|
31
|
+
|
32
|
+
```
|
33
|
+
#### API
|
34
|
+
```ruby
|
35
|
+
require 'tempfile'
|
36
|
+
require 'logger'
|
37
|
+
require 'corruption_monkey'
|
38
|
+
|
39
|
+
file = Tempfile.new('whatever')
|
40
|
+
file_path = file.path
|
41
|
+
logger = Logger.new(nil)
|
42
|
+
|
43
|
+
# flip a bit in a specific bit index
|
44
|
+
BitFlipper.random_bit_flip(file_path, bit: 40, logger: logger)
|
45
|
+
|
46
|
+
# flip a bit in some part of thefile
|
47
|
+
BitFlipper.flip_in_range(file_path, percentile: 4, logger: logger)
|
48
|
+
|
49
|
+
# flip a bit... somewhere ¯\_(ツ)_/¯
|
50
|
+
BitFlipper.random_bit_flip(file_path, logger: logger)
|
51
|
+
|
52
|
+
```
|
53
|
+
|
54
|
+
#### Extra
|
55
|
+
You can generate png glitch art, too.
|
56
|
+

|
57
|
+
|
58
|
+
(From https://en.wikipedia.org/wiki/Portable_Network_Graphics)
|
59
|
+
|
60
|
+
#### ⚠️Caution!
|
61
|
+
This lib may improve your bit flipping skills.
|
data/Rakefile
ADDED
data/bin/flip
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
$LOAD_PATH << 'lib'
|
2
|
+
require 'corruption_monkey/version'
|
3
|
+
|
4
|
+
Gem::Specification.new do |gem|
|
5
|
+
gem.authors = %w(Javier Honduvilla Coto)
|
6
|
+
gem.email = %w(javier.honduvilla@shopify.com javierhonduco@gmail.com)
|
7
|
+
gem.description = 'Bit flipping tool.'
|
8
|
+
gem.summary = 'Library and CLI to flip a bit in a provided file.'
|
9
|
+
gem.homepage = 'https://github.com/Shopify/corruption_monkey'
|
10
|
+
|
11
|
+
gem.files = `git ls-files`.split($\)
|
12
|
+
gem.executables = gem.files.grep(%r{^bin/}).map { |f| File.basename(f) }
|
13
|
+
gem.test_files = gem.files.grep(%r{^test/})
|
14
|
+
gem.name = 'corruption_monkey'
|
15
|
+
gem.require_paths = ['lib']
|
16
|
+
|
17
|
+
gem.version = BitFlipper::VERSION
|
18
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
require 'corruption_monkey'
|
2
|
+
|
3
|
+
module BitFlipper::CLI
|
4
|
+
DOCS = <<-DOCS
|
5
|
+
CLI options:
|
6
|
+
|
7
|
+
1. Specific bit flip (by absolute or relative indexing)
|
8
|
+
$ flip <file_path> (<bit_index>|<percentage of file>%)
|
9
|
+
|
10
|
+
2. Chaos mode (randomized)
|
11
|
+
$ flip <file_path> chaos
|
12
|
+
|
13
|
+
To display help:
|
14
|
+
$ flip help
|
15
|
+
DOCS
|
16
|
+
|
17
|
+
def self.banner(s = $stdout)
|
18
|
+
s.puts '🔥 🔥 🔥 🔥 🔥 🔥 🔥 🔥 🔥 🔥 🔥 🔥 🔥 🔥 '
|
19
|
+
s.puts '🔥 bit flipper chaos 🙈 CLI🔥'
|
20
|
+
s.puts '🔥 🔥 🔥 🔥 🔥 🔥 🔥 🔥 🔥 🔥 🔥 🔥 🔥 🔥 '
|
21
|
+
s.puts
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.start(args = ARGV)
|
25
|
+
file_path = args.shift
|
26
|
+
second_argument = args.shift
|
27
|
+
|
28
|
+
# Empty imput or docs request
|
29
|
+
if (file_path.nil? && second_argument.nil?) ||
|
30
|
+
(!File.file?(file_path) && file_path == 'help')
|
31
|
+
puts DOCS
|
32
|
+
exit 0
|
33
|
+
end
|
34
|
+
|
35
|
+
# First argument is not a (existing) file
|
36
|
+
unless File.file?(file_path)
|
37
|
+
$stderr.puts "The file with path=`#{file_path}` does not exist"
|
38
|
+
exit 1
|
39
|
+
end
|
40
|
+
|
41
|
+
logger = Logger.new(STDOUT)
|
42
|
+
banner
|
43
|
+
|
44
|
+
case second_argument
|
45
|
+
when 'chaos'
|
46
|
+
puts 'chaos mode selected'
|
47
|
+
puts
|
48
|
+
BitFlipper.random_bit_flip(file_path, logger: logger)
|
49
|
+
when /([0-9]{1, 2}|100)%/
|
50
|
+
# this regex feels... weird
|
51
|
+
puts 'percent mode selected'
|
52
|
+
puts
|
53
|
+
BitFlipper.flip_in_range(file_path,
|
54
|
+
percentile: second_argument.to_i,
|
55
|
+
logger: logger)
|
56
|
+
when /[0-9]+/
|
57
|
+
puts 'accurate mode selected'
|
58
|
+
puts
|
59
|
+
BitFlipper.flip!(file_path,
|
60
|
+
bit: second_argument.to_i,
|
61
|
+
logger: logger)
|
62
|
+
else
|
63
|
+
$stderr.puts 'nothing matches!'
|
64
|
+
$stderr.puts DOCS
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'logger'
|
2
|
+
|
3
|
+
module BitFlipper
|
4
|
+
|
5
|
+
BitOutOfBounds = Class.new(Exception)
|
6
|
+
|
7
|
+
def self.flip!(target, bit:, logger: Logger.new(nil))
|
8
|
+
logger.debug "going to flip bit with index=#{bit}"
|
9
|
+
|
10
|
+
flip_byte, flip_bit = bit.divmod(8)
|
11
|
+
|
12
|
+
logger.debug "computed [byte, bit]=#{[flip_byte, flip_bit]}"
|
13
|
+
|
14
|
+
file = File.open(target, 'r+')
|
15
|
+
|
16
|
+
file.seek(flip_byte, 0)
|
17
|
+
byte = file.read(1)
|
18
|
+
|
19
|
+
if byte.nil? || out_of_range(target, bit)
|
20
|
+
raise BitOutOfBounds, "#{bit} is bigger than the offset=#{size_in_bits(target)}"
|
21
|
+
end
|
22
|
+
|
23
|
+
new_byte = [byte.unpack('c').first ^ (1 << flip_bit)].pack('c')
|
24
|
+
|
25
|
+
logger.debug "readed byte: `#{byte}`: #{byte.unpack('b*').first}"
|
26
|
+
logger.debug "writing byte: `#{new_byte}` #{new_byte.unpack('b*').first}"
|
27
|
+
|
28
|
+
file.seek(-1, IO::SEEK_CUR)
|
29
|
+
file.write(new_byte)
|
30
|
+
|
31
|
+
file.fsync
|
32
|
+
file.close
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.random_bit_flip(target, logger: Logger.new(nil))
|
36
|
+
computed_bit = rand(0...size_in_bits(target))
|
37
|
+
flip!(target, bit: computed_bit, logger: logger)
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.flip_in_range(target, percentile:, logger: Logger.new(nil))
|
41
|
+
# TODO(javierhonduco): Improve error checking (╯ರ ~ ರ)
|
42
|
+
bit_to_flip = (size_in_bits(target) * percentile/101.0).to_i
|
43
|
+
|
44
|
+
if out_of_range(target, bit_to_flip)
|
45
|
+
raise IndexError, "percentile=#{percentile} not in range"
|
46
|
+
end
|
47
|
+
|
48
|
+
flip!(target, bit: bit_to_flip, logger: logger)
|
49
|
+
end
|
50
|
+
|
51
|
+
def self.size_in_bits(file)
|
52
|
+
File.size(file) * 8
|
53
|
+
end
|
54
|
+
|
55
|
+
def self.out_of_range(file, bit)
|
56
|
+
bit < 0 || bit > size_in_bits(file)
|
57
|
+
end
|
58
|
+
end
|
data/test/cli_test.rb
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'minitest/autorun'
|
2
|
+
require 'fileutils'
|
3
|
+
require 'corruption_monkey'
|
4
|
+
require_relative 'test_helpers'
|
5
|
+
|
6
|
+
class TestCLI < Minitest::Test
|
7
|
+
include TestHelpers
|
8
|
+
|
9
|
+
TEST_DIR = "/tmp/bit_flipper_monkey_test_cli_#{rand(0..999)}"
|
10
|
+
|
11
|
+
def setup
|
12
|
+
FileUtils.rm_rf(TEST_DIR) # Ensure clean state
|
13
|
+
FileUtils.mkdir(TEST_DIR)
|
14
|
+
end
|
15
|
+
|
16
|
+
def teardown
|
17
|
+
FileUtils.rm_rf(TEST_DIR)
|
18
|
+
end
|
19
|
+
|
20
|
+
def test_accurate_bit_flip
|
21
|
+
test_file = "#{TEST_DIR}/zeroes"
|
22
|
+
zeroes(test_file)
|
23
|
+
|
24
|
+
run_cli(test_file, '9')
|
25
|
+
assert_equal "\u0000\u0002#{"\u0000" * 8}", File.read(test_file)
|
26
|
+
end
|
27
|
+
|
28
|
+
def test_accurate_percentage_flip
|
29
|
+
test_file = "#{TEST_DIR}/zeroes"
|
30
|
+
zeroes(test_file)
|
31
|
+
|
32
|
+
run_cli(test_file, '0%')
|
33
|
+
assert_equal "\u0001#{"\u0000" * 9}", File.read(test_file)
|
34
|
+
end
|
35
|
+
|
36
|
+
def test_random_bit_flip
|
37
|
+
test_file = "#{TEST_DIR}/zeroes"
|
38
|
+
zeroes(test_file)
|
39
|
+
|
40
|
+
run_cli(test_file, 'chaos')
|
41
|
+
assert_equal 1, hamming_weight(test_file)
|
42
|
+
end
|
43
|
+
|
44
|
+
def test_help
|
45
|
+
run_cli =~ /CLI options:/
|
46
|
+
run_cli('help') =~ /CLI options:/
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
def run_cli(*args)
|
52
|
+
output = `bin/flip #{args * ' '}`
|
53
|
+
raise ProcessExecutionError unless $?.success?
|
54
|
+
output
|
55
|
+
end
|
56
|
+
end
|
data/test/lib_test.rb
ADDED
@@ -0,0 +1,103 @@
|
|
1
|
+
require 'minitest/autorun'
|
2
|
+
require 'fileutils'
|
3
|
+
require 'corruption_monkey'
|
4
|
+
require_relative 'test_helpers'
|
5
|
+
|
6
|
+
class TestBitFlipper < Minitest::Test
|
7
|
+
include TestHelpers
|
8
|
+
|
9
|
+
TEST_DIR = "/tmp/bit_flipper_monkey_test_#{rand(0..999)}"
|
10
|
+
|
11
|
+
def setup
|
12
|
+
FileUtils.rm_rf(TEST_DIR) # Ensure clean state
|
13
|
+
FileUtils.mkdir(TEST_DIR)
|
14
|
+
end
|
15
|
+
|
16
|
+
def teardown
|
17
|
+
FileUtils.rm_rf(TEST_DIR)
|
18
|
+
end
|
19
|
+
|
20
|
+
def test_number_of_changed_bits
|
21
|
+
test_file = "#{TEST_DIR}/zeroes"
|
22
|
+
zeroes(test_file)
|
23
|
+
|
24
|
+
BitFlipper.flip!(test_file, bit: 0)
|
25
|
+
assert_equal 1, hamming_weight(test_file)
|
26
|
+
|
27
|
+
BitFlipper.flip!(test_file, bit: 1)
|
28
|
+
assert_equal 2, hamming_weight(test_file)
|
29
|
+
end
|
30
|
+
|
31
|
+
def test_basic_ascii_mathz
|
32
|
+
test_file = "#{TEST_DIR}/simple_ascii"
|
33
|
+
|
34
|
+
File.open(test_file, 'w') do |f|
|
35
|
+
f.write 'a'
|
36
|
+
end
|
37
|
+
|
38
|
+
BitFlipper.flip!(test_file, bit: 0)
|
39
|
+
BitFlipper.flip!(test_file, bit: 1)
|
40
|
+
assert_equal 'b', File.read(test_file)
|
41
|
+
end
|
42
|
+
|
43
|
+
def test_basic_zeroes
|
44
|
+
test_file = "#{TEST_DIR}/zeroes"
|
45
|
+
zeroes(test_file)
|
46
|
+
|
47
|
+
BitFlipper.flip!(test_file, bit: 9)
|
48
|
+
assert_equal "\u0000\u0002#{"\u0000" * 8}", File.read(test_file)
|
49
|
+
end
|
50
|
+
|
51
|
+
def test_twice_flipped_bit
|
52
|
+
test_file = "#{TEST_DIR}/zeroes"
|
53
|
+
base_file = "#{TEST_DIR}/zeroes_DO_NOT_MODIFY"
|
54
|
+
|
55
|
+
[test_file, base_file].each do |file|
|
56
|
+
zeroes(file)
|
57
|
+
end
|
58
|
+
|
59
|
+
BitFlipper.flip!(test_file, bit: 5)
|
60
|
+
BitFlipper.flip!(test_file, bit: 5)
|
61
|
+
assert_equal File.read(base_file), File.read(test_file)
|
62
|
+
|
63
|
+
BitFlipper.flip!(test_file, bit: 2)
|
64
|
+
BitFlipper.flip!(test_file, bit: 2)
|
65
|
+
assert_equal File.read(base_file), File.read(test_file)
|
66
|
+
end
|
67
|
+
|
68
|
+
def test_percentage
|
69
|
+
test_file = "#{TEST_DIR}/zeroes"
|
70
|
+
zeroes(test_file)
|
71
|
+
|
72
|
+
BitFlipper.flip_in_range(test_file, percentile: 0)
|
73
|
+
assert_equal "\u0001#{"\u0000" * 9}", File.read(test_file)
|
74
|
+
zeroes(test_file)
|
75
|
+
|
76
|
+
BitFlipper.flip_in_range(test_file, percentile: 50)
|
77
|
+
assert_equal "#{"\u0000" * 4}\x80#{"\u0000" * 5}", File.read(test_file)
|
78
|
+
zeroes(test_file)
|
79
|
+
|
80
|
+
BitFlipper.flip_in_range(test_file, percentile: 100)
|
81
|
+
assert_equal "#{"\u0000" * 9}\x80", File.read(test_file)
|
82
|
+
end
|
83
|
+
|
84
|
+
def test_bit_out_of_bounds_error
|
85
|
+
test_file = "#{TEST_DIR}/ascii_out_of_bounds"
|
86
|
+
|
87
|
+
File.open(test_file, 'w') do |f|
|
88
|
+
f.write 'a'
|
89
|
+
end
|
90
|
+
|
91
|
+
assert_raises BitFlipper::BitOutOfBounds do
|
92
|
+
BitFlipper.flip!(test_file, bit: 10)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def test_random_bit_flip
|
97
|
+
test_file = "#{TEST_DIR}/zeroes"
|
98
|
+
zeroes(test_file)
|
99
|
+
|
100
|
+
BitFlipper.random_bit_flip(test_file)
|
101
|
+
assert_equal 1, hamming_weight(test_file)
|
102
|
+
end
|
103
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module TestHelpers
|
2
|
+
ProcessExecutionError = Class.new(Exception)
|
3
|
+
|
4
|
+
def zeroes(file)
|
5
|
+
`dd if=/dev/zero of=#{file} bs=1 count=10 2> /dev/null`
|
6
|
+
raise ProcessExecutionError unless $?.success?
|
7
|
+
end
|
8
|
+
|
9
|
+
def hamming_weight(file)
|
10
|
+
File.read(file).
|
11
|
+
unpack('b*').
|
12
|
+
first.
|
13
|
+
split(//).
|
14
|
+
map { |b| b == '1' }.
|
15
|
+
count(true)
|
16
|
+
end
|
17
|
+
end
|
data/travis.yml
ADDED
metadata
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: corruption_monkey
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.2
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Javier
|
8
|
+
- Honduvilla
|
9
|
+
- Coto
|
10
|
+
autorequire:
|
11
|
+
bindir: bin
|
12
|
+
cert_chain: []
|
13
|
+
date: 2016-09-17 00:00:00.000000000 Z
|
14
|
+
dependencies: []
|
15
|
+
description: Bit flipping tool.
|
16
|
+
email:
|
17
|
+
- javier.honduvilla@shopify.com
|
18
|
+
- javierhonduco@gmail.com
|
19
|
+
executables:
|
20
|
+
- flip
|
21
|
+
extensions: []
|
22
|
+
extra_rdoc_files: []
|
23
|
+
files:
|
24
|
+
- ".gitignore"
|
25
|
+
- ".ruby-version"
|
26
|
+
- CONTRIBUTING.md
|
27
|
+
- Gemfile
|
28
|
+
- Gemfile.lock
|
29
|
+
- LICENSE.md
|
30
|
+
- README.md
|
31
|
+
- Rakefile
|
32
|
+
- bin/flip
|
33
|
+
- corruption_monkey.gemspec
|
34
|
+
- lib/corruption_monkey.rb
|
35
|
+
- lib/corruption_monkey/cli.rb
|
36
|
+
- lib/corruption_monkey/version.rb
|
37
|
+
- test/cli_test.rb
|
38
|
+
- test/lib_test.rb
|
39
|
+
- test/test_helpers.rb
|
40
|
+
- travis.yml
|
41
|
+
homepage: https://github.com/Shopify/corruption_monkey
|
42
|
+
licenses: []
|
43
|
+
metadata: {}
|
44
|
+
post_install_message:
|
45
|
+
rdoc_options: []
|
46
|
+
require_paths:
|
47
|
+
- lib
|
48
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
49
|
+
requirements:
|
50
|
+
- - ">="
|
51
|
+
- !ruby/object:Gem::Version
|
52
|
+
version: '0'
|
53
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
54
|
+
requirements:
|
55
|
+
- - ">="
|
56
|
+
- !ruby/object:Gem::Version
|
57
|
+
version: '0'
|
58
|
+
requirements: []
|
59
|
+
rubyforge_project:
|
60
|
+
rubygems_version: 2.5.1
|
61
|
+
signing_key:
|
62
|
+
specification_version: 4
|
63
|
+
summary: Library and CLI to flip a bit in a provided file.
|
64
|
+
test_files:
|
65
|
+
- test/cli_test.rb
|
66
|
+
- test/lib_test.rb
|
67
|
+
- test/test_helpers.rb
|