# See the file LICENSE for redistribution information.
#
# Copyright (c) 2000,2008 Oracle.  All rights reserved.
#
# $Id: fop001.tcl,v 12.10 2008/01/08 20:58:53 bostic Exp $
#
# TEST	fop001.tcl
# TEST	Test file system operations, combined in a transaction. [#7363]
proc fop001 { method { inmem 0 } args } {
	source ./include.tcl

	set args [convert_args $method $args]
	set omethod [convert_method $method]

	# The variable inmem determines whether the test is being
	# run with regular named databases or named in-memory databases.
	if { $inmem == 0 } {
		set tnum "001"
		set string "regular named databases"
		set operator do_op
	} else {
		set tnum "007"
		set string "in-memory named databases"
		set operator do_inmem_op
	}

	puts "\nFop$tnum: ($method)\
	    Two file system ops in one transaction for $string."

	set exists {a b}
	set noexist {foo bar}
	set open {}
	set cases {}
	set ops {rename remove open open_create open_excl truncate}

	# Set up all sensible two-op cases (op1 succeeds).
	foreach retval { 0 "file exists" "no such file" } {
		foreach op1 {rename remove open open_excl \
		    open_create truncate} {
			foreach op2 $ops {
				append cases " " [create_tests $op1 $op2 \
				    $exists $noexist $open $retval]
			}
		}
	}

	# Set up evil two-op cases (op1 fails).  Omit open_create
	# and truncate from op1 list -- open_create always succeeds
	# and truncate requires a successful open.
	foreach retval { 0 "file exists" "no such file" } {
		foreach op1 { rename remove open open_excl } {
			foreach op2 $ops {
				append cases " " [create_badtests $op1 $op2 \
					$exists $noexist $open $retval]
			}
		}
	}

	# The structure of each case is:
	# {{op1 {names1} result end1} {op2 {names2} result}}
	# A result of "0" indicates no error is expected.
	# Otherwise, the result is the expected error message.
	#
	# The "end1" variable indicates whether the first txn
	# ended with an abort or a commit, and is not used
	# in this test.
	#
	# Comment this loop out to remove the list of cases.
#	set i 1
#	foreach case $cases {
#		puts "\tFop$tnum:$i: $case"
#		incr i
#	}

	set testid 0

	# Run all the cases
	foreach case $cases {
		env_cleanup $testdir
		incr testid

		# Extract elements of the case
		set op1 [lindex [lindex $case 0] 0]
		set names1 [lindex [lindex $case 0] 1]
		set res1 [lindex [lindex $case 0] 2]

		set op2 [lindex [lindex $case 1] 0]
		set names2 [lindex [lindex $case 1] 1]
		set res2 [lindex [lindex $case 1] 2]

		puts "\tFop$tnum.$testid: $op1 ($names1), then $op2 ($names2)."

		# The variable 'when' describes when to resolve a txn -- 
		# before or after closing any open databases. 
		foreach when { before after } {

			# Create transactional environment.
			set env [berkdb_env -create -home $testdir -txn]
			error_check_good is_valid_env [is_valid_env $env] TRUE
	
			# Create two databases, dba and dbb.
			if { $inmem == 0 } {
				set dba [eval {berkdb_open -create} $omethod \
				    $args -env $env -auto_commit a]
			} else {
				set dba [eval {berkdb_open -create} $omethod \
				    $args -env $env -auto_commit { "" a }]
			}
			error_check_good dba_open [is_valid_db $dba] TRUE
			error_check_good dba_put [$dba put 1 a] 0
			error_check_good dba_close [$dba close] 0
	
			if { $inmem == 0 } {
				set dbb [eval {berkdb_open -create} $omethod \
				    $args -env $env -auto_commit b]
			} else {
				set dbb [eval {berkdb_open -create} $omethod \
				    $args -env $env -auto_commit { "" b }]
			}
			error_check_good dbb_open [is_valid_db $dbb] TRUE
			error_check_good dbb_put [$dbb put 1 b] 0
			error_check_good dbb_close [$dbb close] 0
	
			# The variable 'end' describes how to resolve the txn.
			# We run the 'abort' first because that leaves the env
			# properly set up for the 'commit' test.
			foreach end {abort commit} {
	
				puts "\t\tFop$tnum.$testid:\
				    $end $when closing database."
	
				# Start transaction
				set txn [$env txn]
	
				# Execute and check operation 1
				set result1 [$operator \
				    $omethod $op1 $names1 $txn $env $args]
				if { $res1 == 0 } {
					error_check_good \
					    op1_should_succeed $result1 $res1
				} else {
					set error [extract_error $result1]
					error_check_good \
					    op1_wrong_failure $error $res1
				}
	
				# Execute and check operation 2
				set result2 [$operator \
				    $omethod $op2 $names2 $txn $env $args]
				if { $res2 == 0 } {
					error_check_good \
					    op2_should_succeed $result2 $res2
				} else {
					set error [extract_error $result2]
					error_check_good \
					    op2_wrong_failure $error $res2
				}
	
				if { $when == "before" } {
					error_check_good txn_$end [$txn $end] 0
		
					# If the txn was aborted, we still
					# have the original two databases.
					if { $end == "abort" } {
						database_exists \
						    $inmem $testdir a
						database_exists \
						    $inmem $testdir b
					}
					close_db_handles 
				} else {
					close_db_handles
					error_check_good txn_$end [$txn $end] 0
	
					if { $end == "abort" } {
						database_exists \
						    $inmem $testdir a
						database_exists \
						    $inmem $testdir b
					}
				}		
			}
	
			# Clean up for next case
			error_check_good env_close [$env close] 0
			error_check_good envremove \
			    [berkdb envremove -home $testdir] 0
			env_cleanup $testdir
		}
	}
}

proc database_exists { inmem testdir name } {
	if { $inmem == 1 } {
		error_check_good db_exists [inmem_exists $testdir $name] 1
	} else {
		error_check_good db_exists [file exists $testdir/$name] 1
	}	
}

# This is a real hack.  We need to figure out if an in-memory named
# file exists.  In a perfect world we could use mpool stat.  Unfortunately,
# mpool_stat returns files that have deadfile set and we need to not consider
# those files to be meaningful.  So, we are parsing the output of db_stat -MA
# (I told you this was a hack)  If we ever change the output, this is going
# to break big time.  Here is what we assume:
# A file is represented by: File #N name
# The last field printed for a file is Flags
# If the file is dead, deadfile will show up in the flags
proc inmem_exists { dir filename } {
      set infile 0
      set islive 0
      set name ""
      set s [exec ./db_stat -MA -h $dir]
      foreach i $s {
              if { $i == "File" } {
                      set infile 1
                      set islive 1
                      set name ""
              } elseif { $i == "Flags" } {
                      set infile 0
                      if { $name != "" && $islive } {
                              return 1
                      }
              } elseif { $infile != 0 } {
                      incr infile
              }

              if { $islive && $i == "deadfile" } {
                      set islive 0
              }

              if { $infile == 3 } {
                      if { $i == $filename } {
                              set name $filename
                      }
              }
      }

      return 0
}

