blob: ee4617554296b2f5f306ab1f4ce92e98138a109a [file] [log] [blame]
#!/usr/bin/env python
#
# schedule_tests.py: testing working copy scheduling
# (adds, deletes, reversion)
#
# Subversion is a tool for revision control.
# See http://subversion.apache.org for more information.
#
# ====================================================================
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF 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
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on 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.
######################################################################
# General modules
import os, logging, stat
logger = logging.getLogger()
# Our testing module
import svntest
# (abbreviation)
Skip = svntest.testcase.Skip_deco
SkipUnless = svntest.testcase.SkipUnless_deco
XFail = svntest.testcase.XFail_deco
Issues = svntest.testcase.Issues_deco
Issue = svntest.testcase.Issue_deco
Wimp = svntest.testcase.Wimp_deco
Item = svntest.wc.StateItem
exp_noop_up_out = svntest.actions.expected_noop_update_output
######################################################################
# Tests
#
# Each test must return on success or raise on failure.
#
#######################################################################
# Stage I - Schedules and modifications, verified with `svn status'
#
# These tests make schedule changes and local mods, and verify that status
# output is as expected. In a second stage, reversion of these changes is
# tested. Potentially, a third stage could test committing these same
# changes.
#
# NOTE: these tests are run within the Stage II tests, not on their own.
#
def add_files(sbox):
"schedule: add some files"
sbox.build(read_only = True)
wc_dir = sbox.wc_dir
# Create some files, then schedule them for addition
delta_path = sbox.ospath('delta')
zeta_path = sbox.ospath('A/B/zeta')
epsilon_path = sbox.ospath('A/D/G/epsilon')
svntest.main.file_append(delta_path, "This is the file 'delta'.")
svntest.main.file_append(zeta_path, "This is the file 'zeta'.")
svntest.main.file_append(epsilon_path, "This is the file 'epsilon'.")
sbox.simple_add('delta', 'A/B/zeta', 'A/D/G/epsilon')
# Make sure the adds show up as such in status
expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
expected_status.add({
'delta' : Item(status='A ', wc_rev=0),
'A/B/zeta' : Item(status='A ', wc_rev=0),
'A/D/G/epsilon' : Item(status='A ', wc_rev=0),
})
svntest.actions.run_and_verify_status(wc_dir, expected_status)
#----------------------------------------------------------------------
def add_directories(sbox):
"schedule: add some directories"
sbox.build(read_only = True)
wc_dir = sbox.wc_dir
# Create some directories, then schedule them for addition
X_path = sbox.ospath('X')
Y_path = sbox.ospath('A/C/Y')
Z_path = sbox.ospath('A/D/H/Z')
os.mkdir(X_path)
os.mkdir(Y_path)
os.mkdir(Z_path)
sbox.simple_add('X', 'A/C/Y', 'A/D/H/Z')
# Make sure the adds show up as such in status
expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
expected_status.add({
'X' : Item(status='A ', wc_rev=0),
'A/C/Y' : Item(status='A ', wc_rev=0),
'A/D/H/Z' : Item(status='A ', wc_rev=0),
})
svntest.actions.run_and_verify_status(wc_dir, expected_status)
#----------------------------------------------------------------------
def nested_adds(sbox):
"schedule: add some nested files and directories"
sbox.build(read_only = True)
wc_dir = sbox.wc_dir
# Create some directories then schedule them for addition
X_path = sbox.ospath('X')
Y_path = sbox.ospath('A/C/Y')
Z_path = sbox.ospath('A/D/H/Z')
os.mkdir(X_path)
os.mkdir(Y_path)
os.mkdir(Z_path)
# Now, create some files and directories to put into our newly added
# directories
P_path = sbox.ospath('X/P')
Q_path = sbox.ospath('A/C/Y/Q')
R_path = sbox.ospath('A/D/H/Z/R')
os.mkdir(P_path)
os.mkdir(Q_path)
os.mkdir(R_path)
delta_path = sbox.ospath('X/delta')
epsilon_path = sbox.ospath('A/C/Y/epsilon')
upsilon_path = sbox.ospath('A/C/Y/upsilon')
zeta_path = sbox.ospath('A/D/H/Z/zeta')
svntest.main.file_append(delta_path, "This is the file 'delta'.")
svntest.main.file_append(epsilon_path, "This is the file 'epsilon'.")
svntest.main.file_append(upsilon_path, "This is the file 'upsilon'.")
svntest.main.file_append(zeta_path, "This is the file 'zeta'.")
# Finally, let's try adding our new files and directories
sbox.simple_add('X', 'A/C/Y', 'A/D/H/Z')
# Make sure the adds show up as such in status
expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
expected_status.add({
'X' : Item(status='A ', wc_rev=0),
'A/C/Y' : Item(status='A ', wc_rev=0),
'A/D/H/Z' : Item(status='A ', wc_rev=0),
'X/P' : Item(status='A ', wc_rev=0),
'A/C/Y/Q' : Item(status='A ', wc_rev=0),
'A/D/H/Z/R' : Item(status='A ', wc_rev=0),
'X/delta' : Item(status='A ', wc_rev=0),
'A/C/Y/epsilon' : Item(status='A ', wc_rev=0),
'A/C/Y/upsilon' : Item(status='A ', wc_rev=0),
'A/D/H/Z/zeta' : Item(status='A ', wc_rev=0),
})
svntest.actions.run_and_verify_status(wc_dir, expected_status)
#----------------------------------------------------------------------
def add_executable(sbox):
"schedule: add some executable files"
sbox.build(read_only = True)
def runTest(wc_dir, fileName, perm, executable):
file_ospath = sbox.ospath(fileName)
if executable:
expected_out = ["*\n"]
expected_err = []
else:
expected_out = []
expected_err = '.*W200017: Property.*not found'
# create an empty file
open(file_ospath, "w")
os.chmod(file_ospath, perm)
sbox.simple_add(fileName)
svntest.actions.run_and_verify_svn(expected_out, expected_err,
'propget', "svn:executable", file_ospath)
test_cases = [
("all_exe", svntest.main.S_ALL_RWX, 1),
("none_exe", svntest.main.S_ALL_RW, 0),
("user_exe", svntest.main.S_ALL_RW | stat.S_IXUSR, 1),
("group_exe", svntest.main.S_ALL_RW | stat.S_IXGRP, 0),
("other_exe", svntest.main.S_ALL_RW | stat.S_IXOTH, 0),
]
for test_case in test_cases:
runTest(sbox.wc_dir, *test_case)
#----------------------------------------------------------------------
def delete_files(sbox):
"schedule: delete some files"
sbox.build(read_only = True)
wc_dir = sbox.wc_dir
# Schedule some files for deletion
sbox.simple_rm('iota', 'A/mu', 'A/D/G/rho', 'A/D/H/omega')
# Make sure the deletes show up as such in status
expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
expected_status.tweak('iota', 'A/mu', 'A/D/G/rho', 'A/D/H/omega',
status='D ')
svntest.actions.run_and_verify_status(wc_dir, expected_status)
#----------------------------------------------------------------------
def delete_dirs(sbox):
"schedule: delete some directories"
sbox.build(read_only = True)
wc_dir = sbox.wc_dir
# Schedule some directories for deletion (this is recursive!)
sbox.simple_rm('A/B/E', 'A/B/F', 'A/D/H')
# Make sure the deletes show up as such in status
expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
expected_status.tweak('A/B/E', 'A/B/E/alpha', 'A/B/E/beta',
'A/B/F',
'A/D/H', 'A/D/H/chi', 'A/D/H/omega', 'A/D/H/psi',
status='D ')
svntest.actions.run_and_verify_status(wc_dir, expected_status)
#######################################################################
# Stage II - Reversion of changes made in Stage I
#
# Each test in Stage II calls the corresponding Stage I test
# and then also tests reversion of those changes.
#
def check_reversion(files, output):
expected_output = []
for file in files:
expected_output = expected_output + ["Reverted '" + file + "'\n"]
output.sort()
expected_output.sort()
if output != expected_output:
logger.warn("Expected output: %s", expected_output)
logger.warn("Actual output: %s", output)
raise svntest.Failure
#----------------------------------------------------------------------
def revert_add_files(sbox):
"revert: add some files"
add_files(sbox)
wc_dir = sbox.wc_dir
# Revert our changes recursively from wc_dir.
delta_path = sbox.ospath('delta')
zeta_path = sbox.ospath('A/B/zeta')
epsilon_path = sbox.ospath('A/D/G/epsilon')
files = [delta_path, zeta_path, epsilon_path]
exit_code, output, err = svntest.actions.run_and_verify_svn(None, [],
'revert',
'--recursive',
wc_dir)
check_reversion(files, output)
#----------------------------------------------------------------------
def revert_add_directories(sbox):
"revert: add some directories"
add_directories(sbox)
wc_dir = sbox.wc_dir
# Revert our changes recursively from wc_dir.
X_path = sbox.ospath('X')
Y_path = sbox.ospath('A/C/Y')
Z_path = sbox.ospath('A/D/H/Z')
files = [X_path, Y_path, Z_path]
exit_code, output, err = svntest.actions.run_and_verify_svn(None, [],
'revert',
'--recursive',
wc_dir)
check_reversion(files, output)
#----------------------------------------------------------------------
def revert_nested_adds(sbox):
"revert: add some nested files and directories"
nested_adds(sbox)
wc_dir = sbox.wc_dir
# Revert our changes recursively from wc_dir.
X_path = sbox.ospath('X')
Y_path = sbox.ospath('A/C/Y')
Z_path = sbox.ospath('A/D/H/Z')
files = ([X_path, Y_path, Z_path]
+ [os.path.join(X_path, child)
for child in ['P', 'delta']]
+ [os.path.join(Y_path, child)
for child in ['Q', 'epsilon', 'upsilon']]
+ [os.path.join(Z_path, child)
for child in ['R', 'zeta']])
exit_code, output, err = svntest.actions.run_and_verify_svn(None, [],
'revert',
'--recursive',
wc_dir)
check_reversion(files, output)
#----------------------------------------------------------------------
@SkipUnless(svntest.main.is_posix_os)
def revert_add_executable(sbox):
"revert: add some executable files"
add_executable(sbox)
wc_dir = sbox.wc_dir
all_path = sbox.ospath('all_exe')
none_path = sbox.ospath('none_exe')
user_path = sbox.ospath('user_exe')
group_path = sbox.ospath('group_exe')
other_path = sbox.ospath('other_exe')
files = [all_path, none_path, user_path, group_path, other_path]
exit_code, output, err = svntest.actions.run_and_verify_svn(None, [],
'revert',
'--recursive',
wc_dir)
check_reversion(files, output)
#----------------------------------------------------------------------
def revert_delete_files(sbox):
"revert: delete some files"
delete_files(sbox)
wc_dir = sbox.wc_dir
# Revert our changes recursively from wc_dir.
iota_path = sbox.ospath('iota')
mu_path = sbox.ospath('A/mu')
rho_path = sbox.ospath('A/D/G/rho')
omega_path = sbox.ospath('A/D/H/omega')
files = [iota_path, mu_path, omega_path, rho_path]
exit_code, output, err = svntest.actions.run_and_verify_svn(None, [],
'revert',
'--recursive',
wc_dir)
check_reversion(files, output)
#----------------------------------------------------------------------
def revert_delete_dirs(sbox):
"revert: delete some directories"
delete_dirs(sbox)
wc_dir = sbox.wc_dir
# Revert our changes recursively from wc_dir.
E_path = sbox.ospath('A/B/E')
F_path = sbox.ospath('A/B/F')
H_path = sbox.ospath('A/D/H')
alpha_path = sbox.ospath('A/B/E/alpha')
beta_path = sbox.ospath('A/B/E/beta')
chi_path = sbox.ospath('A/D/H/chi')
omega_path = sbox.ospath('A/D/H/omega')
psi_path = sbox.ospath('A/D/H/psi')
files = [E_path, F_path, H_path,
alpha_path, beta_path, chi_path, omega_path, psi_path]
exit_code, output, err = svntest.actions.run_and_verify_svn(None, [],
'revert',
'--recursive',
wc_dir)
check_reversion(files, output)
#######################################################################
# Regression tests
#
#----------------------------------------------------------------------
# Regression test for issue #863:
#
# Suppose here is a scheduled-add file or directory which is
# also missing. If I want to make the working copy forget all
# knowledge of the item ("unschedule" the addition), then either 'svn
# revert' or 'svn rm' will make that happen by removing the entry from
# .svn/entries file. While 'svn revert' does with no error,
# 'svn rm' does it with error.
@Issue(863)
def unschedule_missing_added(sbox):
"unschedule addition on missing items"
sbox.build(read_only = True)
wc_dir = sbox.wc_dir
# Create some files and dirs, then schedule them for addition
file1_path = sbox.ospath('file1')
file2_path = sbox.ospath('file2')
dir1_path = sbox.ospath('dir1')
dir2_path = sbox.ospath('dir2')
svntest.main.file_append(file1_path, "This is the file 'file1'.")
svntest.main.file_append(file2_path, "This is the file 'file2'.")
sbox.simple_add('file1', 'file2')
sbox.simple_mkdir('dir1', 'dir2')
# Make sure the 4 adds show up as such in status
expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
expected_status.add({
'file1' : Item(status='A ', wc_rev=0),
'file2' : Item(status='A ', wc_rev=0),
'dir1' : Item(status='A ', wc_rev=0),
'dir2' : Item(status='A ', wc_rev=0),
})
svntest.actions.run_and_verify_status(wc_dir, expected_status)
# Poof, all 4 added things are now missing in action.
os.remove(file1_path)
os.remove(file2_path)
svntest.main.safe_rmtree(dir1_path)
svntest.main.safe_rmtree(dir2_path)
# Unschedule the additions, using 'svn rm' and 'svn revert'.
# FILE1_PATH will throw an error. DIR1_PATH will not since the stub is
# still available in the parent directory.
svntest.main.run_svn(svntest.verify.AnyOutput, 'rm', file1_path)
### actually, the stub does not provide enough information to revert
### the addition, so this command will fail. marking as XFail
sbox.simple_rm('dir1')
sbox.simple_revert('file2', 'dir2')
# 'svn st' should now show absolutely zero local mods.
expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
svntest.actions.run_and_verify_status(wc_dir, expected_status)
#----------------------------------------------------------------------
# Regression test for issue #962:
#
# Make sure 'rm foo; svn rm foo' works on files and directories.
# Also make sure that the deletion is committable.
@Issue(962)
def delete_missing(sbox):
"schedule and commit deletion on missing items"
sbox.build()
wc_dir = sbox.wc_dir
mu_path = sbox.ospath('A/mu')
H_path = sbox.ospath('A/D/H')
# Manually remove a file and a directory.
os.remove(mu_path)
svntest.main.safe_rmtree(H_path)
# Now schedule them for deletion anyway, and make sure no error is output.
sbox.simple_rm('A/mu', 'A/D/H')
# Commit the deletions.
expected_output = svntest.wc.State(wc_dir, {
'A/mu' : Item(verb='Deleting'),
'A/D/H' : Item(verb='Deleting'),
})
expected_status = svntest.actions.get_virginal_state(wc_dir, 2)
expected_status.remove('A/mu', 'A/D/H',
'A/D/H/psi', 'A/D/H/omega', 'A/D/H/chi')
expected_status.tweak(wc_rev=1)
svntest.actions.run_and_verify_commit(wc_dir,
expected_output,
expected_status)
#----------------------------------------------------------------------
# Regression test for issue #854:
# Revert . inside an svn added empty directory should generate an error.
# Not anymore! wc-ng uses absolute paths for everything, which means we
# can handle this case without too much trouble.
@Issue(854)
def revert_inside_newly_added_dir(sbox):
"revert inside a newly added dir"
sbox.build(read_only = True)
# Schedule a new directory for addition
sbox.simple_mkdir('foo')
# Now change into the newly added directory, revert and make sure
# no error is output.
os.chdir(sbox.ospath('foo'))
svntest.main.run_svn(None, 'revert', '.')
#----------------------------------------------------------------------
# Regression test for issue #1609:
# 'svn status' should show a schedule-add directory as 'A' not '?'
@Issue(1609)
def status_add_deleted_directory(sbox):
"status after add of deleted directory"
sbox.build()
wc_dir = sbox.wc_dir
# The original recipe:
#
# svnadmin create repo
# svn mkdir file://`pwd`/repo/foo -m r1
# svn co file://`pwd`/repo wc
# svn rm wc/foo
# rm -rf wc/foo
# svn ci wc -m r2
# svn mkdir wc/foo
A_path = sbox.ospath('A')
sbox.simple_rm('A')
svntest.main.safe_rmtree(A_path)
sbox.simple_commit()
sbox.simple_mkdir('A')
expected_status = svntest.actions.get_virginal_state(wc_dir, 2)
expected_status = svntest.wc.State(wc_dir,
{ '' : Item(status=' ', wc_rev=1),
'A' : Item(status='A ', wc_rev=0),
'iota' : Item(status=' ', wc_rev=1),
})
svntest.actions.run_and_verify_status(wc_dir, expected_status)
# Update will *not* remove the entry for A despite it being marked
# deleted.
svntest.actions.run_and_verify_svn(exp_noop_up_out(2), [],
'up', wc_dir)
expected_status.tweak('', 'iota', wc_rev=2)
svntest.actions.run_and_verify_status(wc_dir, expected_status)
#----------------------------------------------------------------------
# Regression test for issue #939:
# Recursive 'svn add' should still traverse already-versioned dirs.
@Issue(939)
@Issue(4241)
def add_recursive_already_versioned(sbox):
"'svn add' should traverse already-versioned dirs"
sbox.build()
wc_dir = sbox.wc_dir
# Create some files, then schedule them for addition
delta_path = sbox.ospath('delta')
zeta_path = sbox.ospath('A/B/zeta')
epsilon_path = sbox.ospath('A/D/G/epsilon')
svntest.main.file_append(delta_path, "This is the file 'delta'.")
svntest.main.file_append(zeta_path, "This is the file 'zeta'.")
svntest.main.file_append(epsilon_path, "This is the file 'epsilon'.")
# Make sure the adds show up as such in status
expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
expected_status.add({
'delta' : Item(status='A ', wc_rev=0),
'A/B/zeta' : Item(status='A ', wc_rev=0),
'A/D/G/epsilon' : Item(status='A ', wc_rev=0),
})
# Perform the add with the --force flag, and check the status.
### TODO: This part won't work -- you have to be inside the working copy
### or else Subversion will think you're trying to add the working copy
### to its parent directory, and will (possibly, if the parent directory
### isn't versioned) fail.
svntest.main.run_svn(None, 'add', '--force', wc_dir)
svntest.actions.run_and_verify_status(wc_dir, expected_status)
# Now revert, and do the adds again from inside the working copy.
svntest.main.run_svn(None, 'revert', '--recursive', wc_dir)
saved_wd = os.getcwd()
os.chdir(wc_dir)
svntest.main.run_svn(None, 'add', '--force', '.')
os.chdir(saved_wd)
svntest.actions.run_and_verify_status(wc_dir, expected_status)
#----------------------------------------------------------------------
# Regression test for the case where "svn mkdir" outside a working copy
# would create a directory, but then not clean up after itself when it
# couldn't add it to source control.
def fail_add_directory(sbox):
"'svn mkdir' should clean up after itself on error"
# This test doesn't use a working copy
svntest.main.safe_rmtree(sbox.wc_dir)
os.makedirs(sbox.wc_dir)
os.chdir(sbox.wc_dir)
svntest.actions.run_and_verify_svn(None, svntest.verify.AnyOutput,
'mkdir', 'A')
if os.path.exists('A'):
raise svntest.Failure('svn mkdir created an unversioned directory')
#----------------------------------------------------------------------
# Regression test for #2440
# Ideally this test should test for the exit status of the
# 'svn rm non-existent' invocation.
# As the corresponding change to get the exit code of svn binary invoked needs
# a change in many testcase, for now this testcase checks the stderr.
def delete_non_existent(sbox):
"'svn rm non-existent' should exit with an error"
sbox.build(read_only = True)
wc_dir = sbox.wc_dir
os.chdir(wc_dir)
svntest.actions.run_and_verify_svn(None, svntest.verify.AnyOutput,
'rm', '--force', 'non-existent')
#----------------------------------------------------------------------
# Problem encountered by cmpilato when he inadvertantly upset an
# 'svn rm --keep-local' and had to retry it.
def delete_redelete_fudgery(sbox):
"retry of manually upset --keep-local deletion"
sbox.build()
wc_dir = sbox.wc_dir
B_path = os.path.join(wc_dir, 'A', 'B')
# Delete 'A/B' using --keep-local, then remove at the OS level.
svntest.actions.run_and_verify_svn(None, [],
'rm', '--keep-local', B_path)
svntest.main.safe_rmtree(B_path)
# Update the tree.
#
### When WC-NG is running in single-DB mode (one .svn directory and
### database for the whole working copy), I suspect that this update
### will change. Today it re-adds the directory which we just
### scheduled for deletion because the only record of that
### scheduling is stored -- you guessed it -- the directory's .svn/
### area... which we just deleted from disk.
###
### In single-DB-WC-NG-land, though, deleting the directory from
### disk should have no bearing whatsoever on the scheduling
### information stored now in the working copy root's one DB. That
### could change the whole flow of this test, possible leading us to
### remove it as altogether irrelevant. --cmpilato
svntest.actions.run_and_verify_svn(None, [], 'up', wc_dir)
# Now try to run
svntest.actions.run_and_verify_svn(None, [],
'rm', '--keep-local', B_path)
def propset_on_deleted_should_fail(sbox):
"calling svn propset on a deleted node should fail"
sbox.build()
wc_dir = sbox.wc_dir
iota = os.path.join(wc_dir, 'iota')
svntest.actions.run_and_verify_svn(None, [], 'rm', iota)
svntest.actions.run_and_verify_svn(None, "svn: E155023: Can't set propert.*",
'ps', 'prop', 'val', iota)
@Issue(3468)
def replace_dir_delete_child(sbox):
"replace a dir, then delete a child"
# The purpose of this test is to make sure that when a child of a
# replaced directory is deleted, the result can be committed.
sbox.build()
# Replace A/D/H with a copy of A/B
sbox.simple_rm('A/D/H')
sbox.simple_copy('A/B', 'A/D/H')
# Remove two children
sbox.simple_rm('A/D/H/lambda')
sbox.simple_rm('A/D/H/E')
# Don't look at what "svn status" says before commit. It's not clear
# what it should be and that's not the point of this test.
# Commit.
expected_output = svntest.wc.State(sbox.wc_dir, {
'A/D/H' : Item(verb='Replacing'),
'A/D/H/lambda' : Item(verb='Deleting'),
'A/D/H/E' : Item(verb='Deleting'),
})
expected_status = svntest.actions.get_virginal_state(sbox.wc_dir, 1)
expected_status.add({
'A/D/H/F' : Item(status=' ', wc_rev=0),
})
expected_status.tweak('A/D/H', 'A/D/H/F', wc_rev=2)
expected_status.remove('A/D/H/psi', 'A/D/H/omega', 'A/D/H/chi')
svntest.actions.run_and_verify_commit(sbox.wc_dir,
expected_output,
expected_status)
########################################################################
# Run the tests
# list all tests here, starting with None:
test_list = [ None,
revert_add_files,
revert_add_directories,
revert_nested_adds,
revert_add_executable,
revert_delete_files,
revert_delete_dirs,
unschedule_missing_added,
delete_missing,
revert_inside_newly_added_dir,
status_add_deleted_directory,
add_recursive_already_versioned,
fail_add_directory,
delete_non_existent,
delete_redelete_fudgery,
propset_on_deleted_should_fail,
replace_dir_delete_child,
]
if __name__ == '__main__':
svntest.main.run_tests(test_list)
# NOTREACHED
### End of file.