# See the file LICENSE for redistribution information.
#
# Copyright (c) 2004
#	Sleepycat Software.  All rights reserved.
#
# $Id: test109.tcl,v 1.6 2004/09/22 18:01:06 bostic Exp $
#
# TEST	test109
# TEST
# TEST	Test of sequences.
proc test109 { method {tnum "109"} args } {
	source ./include.tcl
	global rand_init
	global fixed_len
	global errorCode

	# Fixed_len must be increased from the default to
	# accommodate fixed-record length methods.
	set orig_fixed_len $fixed_len
	set fixed_len 128
	set args [convert_args $method $args]
	set omethod [convert_method $method]
	error_check_good random_seed [berkdb srand $rand_init] 0

	set eindex [lsearch -exact $args "-env"]
	set txnenv 0
	set seqargs ""
	if { $eindex == -1 } {
		set env NULL
	} else {
		incr eindex
		set env [lindex $args $eindex]
		set txnenv [is_txnenv $env]
		if { $txnenv == 1 } {
			append args " -auto_commit "
			append seqargs " -auto_commit "
		}
		set testdir [get_home $env]
	}

	# Test with in-memory dbs, regular dbs, and subdbs.
	foreach filetype { subdb regular in-memory } {
		puts "Test$tnum: $method ($args) Test of sequences ($filetype)."

		# Skip impossible combinations.
		if { $filetype == "subdb" && [is_queue $method] } {
			puts "Skipping $filetype test for method $method."
			continue
		}
		if { $filetype == "in-memory" && [is_queueext $method] } {
			puts "Skipping $filetype test for method $method."
			continue
		}

		# Reinitialize file name for each file type, then adjust.
		if { $eindex == -1 } {
			set testfile $testdir/test$tnum.db
		} else {
			set testfile test$tnum.db
			set testdir [get_home $env]
		}
		if { $filetype == "subdb" } {
			lappend testfile SUBDB
		}
		if { $filetype == "in-memory" } {
			set testfile ""
		}

		cleanup $testdir $env

		# Make the key numeric so we can test record-based methods.
		set key 1

		# Open a noerr db, since we expect errors.
		set db [eval {berkdb_open_noerr \
		    -create -mode 0644} $args $omethod $testfile]
		error_check_good dbopen [is_valid_db $db] TRUE

		puts "\tTest$tnum.a: Max must be greater than min."
		set errorCode NONE
		catch {set seq [eval berkdb sequence -create \
		    $seqargs -init 0 -min 100 -max 0 $db $key]} res
		error_check_good max>min [is_substr $errorCode EINVAL] 1

		puts "\tTest$tnum.b: Init can't be out of the min-max range."
		set errorCode NONE
		catch {set seq [eval berkdb sequence -create \
			$seqargs -init 101 -min 0 -max 100 $db $key]} res
		error_check_good init [is_substr $errorCode EINVAL] 1

		# Test increment and decrement.
		set min 0
		set max 100
		foreach { init inc } { $min -inc $max -dec } {
			puts "\tTest$tnum.c: Test for overflow error with $inc."
			test_sequence $db $key $min $max $init $inc $seqargs
		}

		# Test cachesize without wrap.  Make sure to test both
		# cachesizes that evenly divide the number of items in the
		# sequence, and that leave unused elements at the end.
		set min 0
		set max 99
		set init 1
		set cachesizes [list 2 7 11]
		foreach csize $cachesizes {
			foreach inc { -inc -dec } {
				puts "\tTest$tnum.d:\
				    -cachesize $csize, $inc, no wrap."
				test_sequence $db $key \
				    $min $max $init $inc $seqargs $csize
			}
		}
		error_check_good db_close [$db close] 0

		# Open a regular db; we expect success on the rest of the tests.
		set db [eval {berkdb_open \
		     -create -mode 0644} $args $omethod $testfile]
		error_check_good dbopen [is_valid_db $db] TRUE

		# Test increment and decrement with wrap.  Cross from negative
		# to positive integers.
		set min -50
		set max 99
		set wrap "-wrap"
		set csize 1
		foreach { init inc } { $min -inc $max -dec } {
			puts "\tTest$tnum.e: Test wrapping with $inc."
			test_sequence $db $key \
			    $min $max $init $inc $seqargs $csize $wrap
		}

		# Test cachesize with wrap.
		set min 0
		set max 99
		set init 0
		set wrap "-wrap"
		foreach csize $cachesizes {
			puts "\tTest$tnum.f: Test -cachesize $csize with wrap."
			test_sequence $db $key \
			    $min $max $init $inc $seqargs $csize $wrap
		}

		# Test multiple handles on the same sequence.
		foreach csize $cachesizes {
			puts "\tTest$tnum.g:\
			    Test multiple handles (-cachesize $csize) with wrap."
			test_sequence $db $key \
			    $min $max $init $inc $seqargs $csize $wrap 1
		}
		error_check_good db_close [$db close] 0
	}
	set fixed_len $orig_fixed_len
	return
}

proc test_sequence { db key min max init \
    {inc "-inc"} {seqargs ""} {csize 1} {wrap "" } {second_handle 0} } {
	global rand_init
	global errorCode

	# The variable "skip" is the cachesize with a direction.
	set skip $csize
	if { $inc == "-dec" } {
		set skip [expr $csize * -1]
	}

	# The "limit" is the closest number to the end of the
	# sequence we can ever see.
	set limit [expr [expr $max + 1] - $csize]
	if { $inc == "-dec" } {
		set limit [expr [expr $min - 1] + $csize]
	}

	# The number of items in the sequence.
	set n [expr [expr $max - $min] + 1]

	# Calculate the number of values returned in the first
	# cycle, and in all other cycles.
	if { $inc == "-inc" } {
		set firstcyclehits \
		    [expr [expr [expr $max - $init] + 1] / $csize]
	} elseif { $inc == "-dec" } {
		set firstcyclehits \
		    [expr [expr [expr $init - $min] + 1] / $csize]
	} else {
		puts "FAIL: unknown inc flag $inc"
	}
	set hitspercycle [expr $n / $csize]

	# Create the sequence.
	set seq [eval {berkdb sequence} -create -cachesize $csize $seqargs \
	    $wrap -init $init -min $min -max $max $inc $db $key]
	error_check_good is_valid_seq [is_valid_seq $seq] TRUE
	if { $second_handle == 1 } {
		set seq2 [eval {berkdb sequence} -create $seqargs $db $key]
		error_check_good is_valid_seq2 [is_valid_seq $seq2] TRUE
	}

	# Exercise get options.
	set getdb [$seq get_db]
	error_check_good seq_get_db $getdb $db

	set flags [$seq get_flags]
	set exp_flags [list $inc $wrap]
	foreach item $exp_flags {
		if { [llength $item] == 0 } {
			set idx [lsearch -exact $exp_flags $item]
			set exp_flags [lreplace $exp_flags $idx $idx]
		}
	}
	error_check_good get_flags $flags $exp_flags

	set range [$seq get_range]
	error_check_good get_range_min [lindex $range 0] $min
	error_check_good get_range_max [lindex $range 1] $max

	set cache [$seq get_cachesize]
	error_check_good get_cachesize $cache $csize

	# Within the loop, for each successive seq get we calculate
	# the value we expect to receive, then do the seq get and
	# compare.
	#
	# Always test some multiple of the number of items in the
	# sequence; this tests overflow and wrap-around.
	#
	set mult 2
	for { set i 0 } { $i < [expr $n * $mult] } { incr i } {
		#
		# Calculate expected return value.
		#
		# On the first cycle, start from init.
		set expected [expr $init + [expr $i * $skip]]
		if { $i >= $firstcyclehits && $wrap != "-wrap" } {
			set expected "overflow"
		}

		# On second and later cycles, start from min or max.
		# We do a second cycle only if wrapping is specified.
		if { $wrap == "-wrap" } {
			if { $inc == "-inc" && $expected > $limit } {
				set j [expr $i - $firstcyclehits]
				while { $j >= $hitspercycle } {
					set j [expr $j - $hitspercycle]
				}
				set expected [expr $min + [expr $j * $skip]]
			}

			if { $inc == "-dec" && $expected < $limit } {
				set j [expr $i - $firstcyclehits]
				while { $j >= $hitspercycle } {
					set j [expr $j - $hitspercycle]
				}
				set expected [expr $max + [expr $j * $skip]]
			}
		}

		# Get return value.  If we've got a second handle, choose
		# randomly which handle does the seq get.
		set syncarg ""
		if { $seqargs != "" } {
			set syncarg " -nosync "
		}
		set errorCode NONE
		if { $second_handle == 0 } {
			catch {eval {$seq get} $seqargs $syncarg $csize} res
		} elseif { [berkdb random_int 0 1] == 0 } {
			catch {eval {$seq get} $seqargs $syncarg $csize} res
		} else {
			catch {eval {$seq2 get} $seqargs $syncarg $csize} res
		}

		# Compare expected to actual value.
		if { $expected == "overflow" } {
			error_check_good overflow [is_substr $errorCode EINVAL] 1
		} else {
			error_check_good seq_get_wrap $res $expected
		}
	}

	# A single handle requires a 'seq remove', but a second handle
	# should be closed, and then we can remove the sequence.
	if { $second_handle == 1 } {
		error_check_good seq2_close [$seq2 close] 0
	}
	error_check_good seq_remove [eval {$seq remove} $seqargs] 0
}
