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 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
@@ -0,0 +1,5 @@
1
+ #### How to contribute to corruption_monkey
2
+
3
+ Open an issue/ pull request :)
4
+
5
+ Thanks! :revolving_hearts:
data/Gemfile ADDED
@@ -0,0 +1,8 @@
1
+ source 'https://rubygems.org'
2
+
3
+ group :development, :test do
4
+ gem 'rake'
5
+ gem 'minitest'
6
+ end
7
+
8
+ gemspec
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
+ ![image](https://cloud.githubusercontent.com/assets/959128/17756485/235c13ae-64ae-11e6-8583-5e606bcb45f3.png)
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
@@ -0,0 +1,9 @@
1
+ require 'rake/testtask'
2
+ require 'bundler/setup'
3
+ require 'bundler/gem_tasks'
4
+
5
+ task default: :test
6
+
7
+ Rake::TestTask.new do |t|
8
+ t.test_files = FileList['test/*test.rb']
9
+ end
data/bin/flip ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'corruption_monkey/cli'
4
+
5
+ BitFlipper::CLI.start
@@ -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,3 @@
1
+ module BitFlipper
2
+ VERSION = '0.1.2'
3
+ 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
@@ -0,0 +1,4 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.1
4
+ - 2.3.1
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