2013-11-19 18:21:00 -05:00
#!/usr/bin/env ruby
2014-01-06 16:48:02 -05:00
# Licensed to Elasticsearch under one or more contributor
# license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright
# ownership. Elasticsearch licenses this file to you under
# the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License.
# You may obtain a copy of the License at
2013-11-19 18:21:00 -05:00
#
# http://www.apache.org/licenses/LICENSE-2.0
#
2014-01-06 16:48:02 -05:00
# Unless required by applicable law or agreed to in writing,
2014-04-11 19:05:22 -04:00
# software distributed under the License is distributed on
2014-01-06 16:48:02 -05:00
# an 'AS IS' BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
# either express or implied. See the License for the specific
# language governing permissions and limitations under the License
2013-11-19 18:21:00 -05:00
#
2014-04-11 19:05:22 -04:00
# NAME
# build_randomization.rb -- Generate property file for the JDK randomization test
#
# SYNOPSIS
# build_randomization.rb [-d] [-l|t]
#
# DESCRIPTION
# This script takes the randomization choices described in RANDOM_CHOICE and generates apporpriate JAVA property file 'prop.txt'
# This property file also contain the appropriate JDK selection, randomized. JDK randomization is based on what is available on the Jenkins tools
# directory. This script is used by Jenkins test system to conduct Elasticsearch server randomization testing.
#
# In hash RANDOM_CHOISES, the key of randomization hash maps to key of java property. The value of the hash describes the possible value of the randomization
2013-11-19 18:21:00 -05:00
#
2014-04-11 19:05:22 -04:00
# For example RANDOM_CHOICES = { 'es.node.mode' => {:choices => ['local', 'network'], :method => :get_random_one} } means
# es.node.mode will be set to either 'local' or 'network', each with 50% of probability
#
# OPTIONS SUMMARY
# The options are as follows:
#
# -d, --debug Increase logging verbosity for debugging purpose
# -t, --test Run in test mode. The script will execute unit tests.
# -l, --local Run in local mode. In this mode, directory structure will be created under current directory to mimick
# Jenkins' server directory layout. This mode is mainly used for development.
require 'enumerator'
require 'getoptlong'
require 'log4r'
require 'optparse'
require 'rubygems'
2013-11-19 18:21:00 -05:00
require 'yaml'
2014-04-11 19:05:22 -04:00
include Log4r
RANDOM_CHOICES = {
'tests.jvm.argline' = > [
{ :choices = > [ '-server' ] , :method = > 'get_random_one' } ,
{ :choices = > [ '-XX:+UseConcMarkSweepGC' , '-XX:+UseParallelGC' , '-XX:+UseSerialGC' , '-XX:+UseG1GC' ] , :method = > 'get_random_one' } ,
2014-09-02 03:53:56 -04:00
{ :choices = > [ '-XX:+UseCompressedOops' , '-XX:-UseCompressedOops' ] , :method = > 'get_random_one' } ,
{ :choices = > [ '-XX:+AggressiveOpts' ] , :method = > 'get_50_percent' }
2014-04-11 19:05:22 -04:00
] ,
'es.node.mode' = > { :choices = > [ 'local' , 'network' ] , :method = > 'get_random_one' } ,
# bug forced to be false for now :test_nightly => { :method => :true_or_false},
'tests.nightly' = > { :selections = > false } ,
2014-08-21 07:43:30 -04:00
'tests.heap.size' = > { :choices = > [ 512 , 1024 ] , :method = > :random_heap } ,
2014-04-11 19:05:22 -04:00
'tests.assertion.disabled' = > { :choices = > 'org.elasticsearch' , :method = > 'get_10_percent' } ,
'tests.security.manager' = > { :choices = > [ true , false ] , :method = > 'get_90_percent' } ,
}
L = Logger . new 'test_randomizer'
L . outputters = Outputter . stdout
L . level = INFO
C = { :local = > false , :test = > false }
OptionParser . new do | opts |
opts . banner = " Usage: build_ranodimzatin.rb [options] "
opts . on ( " -d " , " --debug " , " Debug mode " ) do | d |
L . level = DEBUG
end
opts . on ( " -l " , " --local " , " Run in local mode " ) do | l |
C [ :local ] = true
end
opts . on ( " -t " , " --test " , " Run unit tests " ) do | t |
C [ :test ] = true
end
end . parse!
class Randomizer
attr_accessor :data_array
def initialize ( data_array )
@data_array = data_array
end
def true_or_false
[ true , false ] [ rand ( 2 ) ]
end
2014-08-21 07:43:30 -04:00
def random_heap
inner_data_array = [ data_array [ 0 ] , data_array [ 1 ] , data_array [ 0 ] + rand ( data_array [ 1 ] - data_array [ 0 ] ) ]
" %sm " % inner_data_array [ rand ( inner_data_array . size ) ]
end
2014-04-11 19:05:22 -04:00
def get_random_with_distribution ( mdata_array , distribution )
L . debug " randomized distribution data %s " % YAML . dump ( mdata_array )
L . debug " randomized distribution distribution %s " % YAML . dump ( distribution )
carry = 0
distribution_map = distribution . enum_for ( :each_with_index ) . map { | x , i | pre_carry = carry ; carry += x ; { i = > x + pre_carry } }
random_size = distribution_map . last . values . first
selection = rand ( random_size )
#get the index that randomize choice mapped to
choice = distribution_map . select do | x |
x . values . first > selection #only keep the index with distribution value that is higher than the random generated number
end . first . keys . first #first hash's first key is the index we want
L . debug ( " randomized distribution choice %s " % mdata_array [ choice ] )
mdata_array [ choice ]
end
def get_random_one
data_array [ rand ( data_array . size ) ]
end
def method_missing ( meth , * args , & block )
# trap randomization based on percentage
if meth . to_s =~ / ^get_( \ d+)_percent /
percentage = $1 . to_i
remain = 100 - percentage
#data = args.first
normalized_data = if ( ! data_array . kind_of? ( Array ) )
[ data_array , nil ]
else
data_array
end
get_random_with_distribution ( normalized_data , [ percentage , remain ] )
else
super
end
end
end
2013-11-19 18:21:00 -05:00
class JDKSelector
attr_reader :directory , :jdk_list
def initialize ( directory )
@directory = directory
end
2014-04-11 19:05:22 -04:00
# get selection of available JDKs from Jenkins automatic install directory
2013-11-19 18:21:00 -05:00
def get_jdk
2014-04-11 19:05:22 -04:00
@jdk_list = Dir . entries ( directory ) . select do | x |
x . chars . first == 'J'
2013-11-19 18:21:00 -05:00
end . map do | y |
File . join ( directory , y )
end
self
end
2014-04-11 19:05:22 -04:00
def filter_java_6 ( files )
files . select { | i | File . basename ( i ) . split ( / [^0-9] / ) [ - 1 ] . to_i > 6 }
end
# do randomized selection from a given array
2013-11-19 18:21:00 -05:00
def select_one ( selection_array = nil )
2014-02-26 14:33:27 -05:00
selection_array = filter_java_6 ( selection_array || @jdk_list )
2014-04-11 19:05:22 -04:00
Randomizer . new ( selection_array ) . get_random_one
end
2014-02-26 14:33:27 -05:00
2014-04-11 19:05:22 -04:00
def JDKSelector . generate_jdk_hash ( jdk_choice )
file_separator = if Gem . win_platform?
File :: ALT_SEPARATOR
else
File :: SEPARATOR
end
{
:PATH = > [ jdk_choice , 'bin' ] . join ( file_separator ) + File :: PATH_SEPARATOR + ENV [ 'PATH' ] ,
:JAVA_HOME = > jdk_choice
}
2013-11-19 18:21:00 -05:00
end
end
2014-04-11 19:05:22 -04:00
#
# Fix argument JDK selector
#
class FixedJDKSelector < JDKSelector
def initialize ( directory )
@directory = [ * directory ] #selection of directories to pick from
end
def get_jdk
#since JDK selection is already specified..jdk list is the @directory
@jdk_list = @directory
self
end
def select_one ( selection_array = nil )
#bypass filtering since this is not automatic
selection_array || = @jdk_list
Randomizer . new ( selection_array ) . get_random_one
end
2013-11-21 14:03:22 -05:00
end
2014-04-11 19:05:22 -04:00
#
# Property file writer
#
class PropertyWriter
attr_reader :working_directory
def initialize ( mworking_directory )
@working_directory = mworking_directory
end
# # pick first element out of array of hashes, generate write java property file
def generate_property_file ( data )
directory = working_directory
#array transformation
content = data . to_a . map do | x |
x . join ( '=' )
end . sort
file_name = ( ENV [ 'BUILD_ID' ] + ENV [ 'BUILD_NUMBER' ] ) || 'prop' rescue 'prop'
file_name = file_name . split ( File :: SEPARATOR ) . first + '.txt'
L . debug " Property file name is %s " % file_name
File . open ( File . join ( directory , file_name ) , 'w' ) do | file |
file . write ( content . join ( " \n " ) )
end
end
2014-02-26 14:33:27 -05:00
end
2014-04-11 19:05:22 -04:00
#
# Execute randomization logics
#
class RandomizedRunner
attr_reader :random_choices , :jdk , :p_writer
def initialize ( mrandom_choices , mjdk , mwriter )
@random_choices = mrandom_choices
@jdk = mjdk
@p_writer = mwriter
end
def generate_selections
configuration = random_choices
L . debug " Enter %s " % __method__
L . debug " Configuration %s " % YAML . dump ( configuration )
generated = { }
configuration . each do | k , v |
if ( v . kind_of? ( Hash ) )
if ( v . has_key? ( :method ) )
randomizer = Randomizer . new ( v [ :choices ] )
v [ :selections ] = randomizer . __send__ ( v [ :method ] )
end
else
v . each do | x |
if ( x . has_key? ( :method ) )
randomizer = Randomizer . new ( x [ :choices ] )
x [ :selections ] = randomizer . __send__ ( x [ :method ] )
end
end
end
end . each do | k , v |
if ( v . kind_of? ( Array ) )
selections = v . inject ( [ ] ) do | sum , current_hash |
sum . push ( current_hash [ :selections ] )
end
else
selections = [ v [ :selections ] ] unless v [ :selections ] . nil?
end
generated [ k ] = selections unless ( selections . nil? || selections . size == 0 )
end
L . debug " Generated selections %s " % YAML . dump ( generated )
generated
end
def get_env_matrix ( jdk_selection , selections )
L . debug " Enter %s " % __method__
#normalization
s = { }
selections . each do | k , v |
if ( v . size > 1 )
2014-09-02 03:53:56 -04:00
s [ k ] = v . compact . join ( ' ' ) #this should be dependent on class of v[0] and perform reduce operation instead... good enough for now
2014-04-11 19:05:22 -04:00
else
s [ k ] = v . first
end
end
j = JDKSelector . generate_jdk_hash ( jdk_selection )
# create build description line
desc = { }
# TODO: better error handling
2014-08-21 07:43:30 -04:00
desc [ :BUILD_DESC ] = " %s,%s,heap[%s],%s%s%s%s " % [
2014-04-11 19:05:22 -04:00
File . basename ( j [ :JAVA_HOME ] ) ,
s [ 'es.node.mode' ] ,
2014-08-21 07:43:30 -04:00
s [ 'tests.heap.size' ] ,
2014-04-11 19:05:22 -04:00
s [ 'tests.nightly' ] ? 'nightly,' :'' ,
s [ 'tests.jvm.argline' ] . gsub ( / -XX: / , '' ) ,
s . has_key? ( 'tests.assertion.disabled' ) ? ',assert off' : '' ,
s [ 'tests.security.manager' ] ? ',sec manager on' : ''
]
result = j . merge ( s ) . merge ( desc )
L . debug ( YAML . dump ( result ) )
result
end
def run!
p_writer . generate_property_file ( get_env_matrix ( jdk , generate_selections ) )
end
end
#
# Main
#
unless ( C [ :test ] )
# Check to see if this is running locally
unless ( C [ :local ] )
L . debug ( " Normal Mode " )
working_directory = ENV . fetch ( 'WORKSPACE' , ( Gem . win_platform? ? Dir . pwd : '/var/tmp' ) )
else
L . debug ( " Local Mode " )
test_directory = 'tools/hudson.model.JDK/'
unless ( File . exist? ( test_directory ) )
L . info " running local mode, setting up running environment "
L . info " properties are written to file prop.txt "
FileUtils . mkpath " %sJDK6 " % test_directory
FileUtils . mkpath " %sJDK7 " % test_directory
end
working_directory = Dir . pwd
end
# script support both window and linux
# TODO: refactor into platform/machine dependent class structure
jdk = if ( Gem . win_platform? )
#window mode jdk directories are fixed
#TODO: better logic
L . debug ( " Window Mode " )
if ( File . directory? ( 'y:\jdk7\7u55' ) ) #old window system under ec2
FixedJDKSelector . new ( 'y:\jdk7\7u55' )
else #new metal window system
FixedJDKSelector . new ( [ 'c:\PROGRA~1\JAVA\jdk1.8.0_05' , 'c:\PROGRA~1\JAVA\jdk1.7.0_55' ] )
end
else
#Jenkins sets pwd prior to execution
L . debug ( " Linux Mode " )
JDKSelector . new ( File . join ( ENV [ 'PWD' ] , 'tools' , 'hudson.model.JDK' ) )
end
runner = RandomizedRunner . new ( RANDOM_CHOICES ,
jdk . get_jdk . select_one ,
PropertyWriter . new ( working_directory ) )
environment_matrix = runner . run!
exit 0
else
require " test/unit "
end
#
# Test
#
class TestJDKSelector < Test :: Unit :: TestCase
L = Logger . new 'test'
L . outputters = Outputter . stdout
L . level = DEBUG
def test_hash_generator
jdk_choice = '/dummy/jdk7'
generated = JDKSelector . generate_jdk_hash ( jdk_choice )
L . debug " Generated %s " % generated
assert generated [ :PATH ] . include? ( jdk_choice ) , " PATH doesn't included choice "
assert generated [ :JAVA_HOME ] . include? ( jdk_choice ) , " JAVA home doesn't include choice "
2013-11-19 18:21:00 -05:00
end
end
2014-04-11 19:05:22 -04:00
class TestFixJDKSelector < Test :: Unit :: TestCase
L = Logger . new 'test'
L . outputters = Outputter . stdout
L . level = DEBUG
def test_initialize
[ '/home/dummy' , [ '/JDK7' , '/home2' ] , [ 'home/dummy' ] ] . each do | x |
test_object = FixedJDKSelector . new ( x )
assert_kind_of Array , test_object . directory
assert_equal [ * x ] , test_object . directory
end
2013-11-19 18:21:00 -05:00
end
2014-04-11 19:05:22 -04:00
def test_select_one
test_array = %w( one two three )
test_object = FixedJDKSelector . new ( test_array )
assert test_array . include? ( test_object . get_jdk . select_one )
end
def test_hash_generator
jdk_choice = '/dummy/jdk7'
generated = FixedJDKSelector . generate_jdk_hash ( jdk_choice )
L . debug " Generated %s " % generated
assert generated [ :PATH ] . include? ( jdk_choice ) , " PATH doesn't included choice "
assert generated [ :JAVA_HOME ] . include? ( jdk_choice ) , " JAVA home doesn't include choice "
2013-11-19 18:21:00 -05:00
end
end
2014-04-11 19:05:22 -04:00
class TestPropertyWriter < Test :: Unit :: TestCase
L = Logger . new 'test'
L . outputters = Outputter . stdout
L . level = DEBUG
def test_initialize
[ '/home/dummy' , '/tmp' ] . each do | x |
test_object = PropertyWriter . new ( x )
assert_kind_of String , test_object . working_directory
assert_equal x , test_object . working_directory
end
end
def test_generate_property
test_file = '/tmp/prop.txt'
File . delete ( test_file ) if File . exist? ( test_file )
test_object = PropertyWriter . new ( File . dirname ( test_file ) )
# default prop.txt
test_object . generate_property_file ( { :hi = > 'there' } )
assert ( File . exist? ( test_file ) )
File . open ( test_file , 'r' ) do | properties_file |
properties_file . read . each_line do | line |
line . strip!
assert_equal 'hi=there' , line , " content %s is not hi=there " % line
end
end
File . delete ( test_file ) if File . exist? ( test_file )
end
end
class DummyPropertyWriter < PropertyWriter
def generate_property_file ( data )
L . debug " generating property file for %s " % YAML . dump ( data )
L . debug " on directory %s " % working_directory
2014-01-17 15:52:22 -05:00
end
end
2013-11-19 18:21:00 -05:00
2014-04-11 19:05:22 -04:00
class TestRandomizedRunner < Test :: Unit :: TestCase
def test_initialize
test_object = RandomizedRunner . new ( RANDOM_CHOICES , '/tmp/dummy/jdk' , po = PropertyWriter . new ( '/tmp' ) )
assert_equal RANDOM_CHOICES , test_object . random_choices
assert_equal '/tmp/dummy/jdk' , test_object . jdk
assert_equal po , test_object . p_writer
end
def test_generate_selection_no_method
test_object = RandomizedRunner . new ( { 'tests.one' = > { :selections = > false } } , '/tmp/dummy/jdk' , po = DummyPropertyWriter . new ( '/tmp' ) )
selection = test_object . generate_selections
assert_equal false , selection [ 'tests.one' ] . first , 'randomization without selection method fails'
end
def test_generate_with_method
test_object = RandomizedRunner . new ( { 'es.node.mode' = > { :choices = > [ 'local' , 'network' ] , :method = > 'get_random_one' } } ,
'/tmp/dummy/jdk' , po = DummyPropertyWriter . new ( '/tmp' ) )
selection = test_object . generate_selections
assert [ 'local' , 'network' ] . include? ( selection [ 'es.node.mode' ] . first ) , 'selection choice is not correct'
end
def test_get_env_matrix
test_object = RandomizedRunner . new ( RANDOM_CHOICES ,
'/tmp/dummy/jdk' , po = DummyPropertyWriter . new ( '/tmp' ) )
selection = test_object . generate_selections
env_matrix = test_object . get_env_matrix ( '/tmp/dummy/jdk' , selection )
puts YAML . dump ( env_matrix )
assert_equal '/tmp/dummy/jdk' , env_matrix [ :JAVA_HOME ]
end
end