/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar
292 by Martin Pool
- start adding a pure-python blackbox test suite
1
#! /usr/bin/python
561 by Martin Pool
- add failing test for problem with stat after remove,
2
# -*- coding: utf-8 -*-
292 by Martin Pool
- start adding a pure-python blackbox test suite
3
4
# Copyright (C) 2005 Canonical Ltd
5
6
# This program is free software; you can redistribute it and/or modify
7
# it under the terms of the GNU General Public License as published by
8
# the Free Software Foundation; either version 2 of the License, or
9
# (at your option) any later version.
10
11
# This program is distributed in the hope that it will be useful,
12
# but WITHOUT ANY WARRANTY; without even the implied warranty of
13
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
# GNU General Public License for more details.
15
16
# You should have received a copy of the GNU General Public License
17
# along with this program; if not, write to the Free Software
18
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19
728 by Martin Pool
- warning not to use testbzr
20
print 'please use "bzr selftest" instead'
21
import sys
22
sys.exit(1)
23
24
25
292 by Martin Pool
- start adding a pure-python blackbox test suite
26
27
"""External black-box test for bzr.
28
29
This always runs bzr as an external process to try to catch bugs
30
related to argument processing, startup, etc.
31
507 by Martin Pool
- new -p option for testbzr to use a different version of python
32
usage:
33
34
    testbzr [-p PYTHON] [BZR]
35
36
By default this tests the copy of bzr found in the same directory as
37
testbzr, or the first one found on the $PATH.  A copy of bzr may be
38
given on the command line to override this, for example when applying
39
a new test suite to an old copy of bzr or vice versa.
40
41
testbzr normally invokes bzr using the same version of python as it
42
would normally use to run -- that is, the system default python,
43
unless that is older than 2.3.  The -p option allows specification of
44
a different Python interpreter, such as when testing that bzr still
45
works on python2.3.
46
292 by Martin Pool
- start adding a pure-python blackbox test suite
47
This replaces the previous test.sh which was not very portable."""
48
296 by Martin Pool
- better reports from testbzr when it fails
49
import sys, os, traceback
339 by Martin Pool
many more diffs
50
from os import mkdir
51
from os.path import exists
292 by Martin Pool
- start adding a pure-python blackbox test suite
52
335 by Martin Pool
- add new failing test for command parsing
53
TESTDIR = "testbzr.tmp"
54
624 by Martin Pool
- make sure bzr is always explicitly invoked through
55
56
# we always invoke bzr as 'python bzr' (or e.g. 'python2.3 bzr')
57
# partly so as to cope if the bzr binary is not marked executable
58
OVERRIDE_PYTHON = 'python'
507 by Martin Pool
- new -p option for testbzr to use a different version of python
59
335 by Martin Pool
- add new failing test for command parsing
60
LOGFILENAME = 'testbzr.log'
61
292 by Martin Pool
- start adding a pure-python blackbox test suite
62
try:
63
    import shutil
301 by Martin Pool
- provide for catching output from shell commands
64
    from subprocess import call, Popen, PIPE
292 by Martin Pool
- start adding a pure-python blackbox test suite
65
except ImportError, e:
66
    sys.stderr.write("testbzr: sorry, this test suite requires modules from python2.4\n"
67
                     + '    ' + str(e))
68
    sys.exit(1)
69
70
296 by Martin Pool
- better reports from testbzr when it fails
71
72
302 by Martin Pool
testbzr: new backtick() helper
73
def formcmd(cmd):
74
    if isinstance(cmd, basestring):
75
        cmd = cmd.split()
76
398 by Martin Pool
- testbzr finds the right version of bzr to test
77
    if cmd[0] == 'bzr':
78
        cmd[0] = BZRPATH
507 by Martin Pool
- new -p option for testbzr to use a different version of python
79
        if OVERRIDE_PYTHON:
80
            cmd.insert(0, OVERRIDE_PYTHON)
398 by Martin Pool
- testbzr finds the right version of bzr to test
81
507 by Martin Pool
- new -p option for testbzr to use a different version of python
82
    logfile.write('$ %r\n' % cmd)
83
    
302 by Martin Pool
testbzr: new backtick() helper
84
    return cmd
85
86
87
def runcmd(cmd, retcode=0):
300 by Martin Pool
- more tests
88
    """Run one command and check the return code.
292 by Martin Pool
- start adding a pure-python blackbox test suite
89
301 by Martin Pool
- provide for catching output from shell commands
90
    Returns a tuple of (stdout,stderr) strings.
91
292 by Martin Pool
- start adding a pure-python blackbox test suite
92
    If a single string is based, it is split into words.
93
    For commands that are not simple space-separated words, please
94
    pass a list instead."""
302 by Martin Pool
testbzr: new backtick() helper
95
    cmd = formcmd(cmd)
96
    log_linenumber()
97
    
98
    actual_retcode = call(cmd, stdout=logfile, stderr=logfile)
99
    
100
    if retcode != actual_retcode:
101
        raise CommandFailed("test failed: %r returned %d, expected %d"
102
                            % (cmd, actual_retcode, retcode))
103
104
105
106
def backtick(cmd, retcode=0):
107
    cmd = formcmd(cmd)
108
    log_linenumber()
109
    child = Popen(cmd, stdout=PIPE, stderr=logfile)
301 by Martin Pool
- provide for catching output from shell commands
110
    outd, errd = child.communicate()
302 by Martin Pool
testbzr: new backtick() helper
111
    logfile.write(outd)
301 by Martin Pool
- provide for catching output from shell commands
112
    actual_retcode = child.wait()
307 by Martin Pool
testbzr: clean up crlf handling
113
114
    outd = outd.replace('\r', '')
301 by Martin Pool
- provide for catching output from shell commands
115
    
298 by Martin Pool
- test some commands known to fail
116
    if retcode != actual_retcode:
117
        raise CommandFailed("test failed: %r returned %d, expected %d"
118
                            % (cmd, actual_retcode, retcode))
292 by Martin Pool
- start adding a pure-python blackbox test suite
119
302 by Martin Pool
testbzr: new backtick() helper
120
    return outd
121
301 by Martin Pool
- provide for catching output from shell commands
122
292 by Martin Pool
- start adding a pure-python blackbox test suite
123
124
def progress(msg):
125
    print '* ' + msg
126
    logfile.write('* '+ msg + '\n')
297 by Martin Pool
- fix intentional testcase failure
127
    log_linenumber()
128
129
299 by Martin Pool
testbzr:
130
def cd(dirname):
131
    logfile.write('$ cd %s\n' % dirname)
132
    os.chdir(dirname)
133
134
300 by Martin Pool
- more tests
135
297 by Martin Pool
- fix intentional testcase failure
136
def log_linenumber():
137
    """Log the stack frame location two things up."""
138
    stack = traceback.extract_stack()[-3]
139
    logfile.write('   at %s:%d\n' % stack[:2])
140
141
340 by Martin Pool
- more testcase fixes
142
292 by Martin Pool
- start adding a pure-python blackbox test suite
143
# prepare an empty scratch directory
144
if os.path.exists(TESTDIR):
145
    shutil.rmtree(TESTDIR)
146
513 by Martin Pool
- show some log output if the tests fail
147
start_dir = os.getcwd()
148
292 by Martin Pool
- start adding a pure-python blackbox test suite
149
335 by Martin Pool
- add new failing test for command parsing
150
logfile = open(LOGFILENAME, 'wt', buffering=1)
292 by Martin Pool
- start adding a pure-python blackbox test suite
151
300 by Martin Pool
- more tests
152
try:
507 by Martin Pool
- new -p option for testbzr to use a different version of python
153
    from getopt import getopt
154
    opts, args = getopt(sys.argv[1:], 'p:')
155
156
    for option, value in opts:
157
        if option == '-p':
158
            OVERRIDE_PYTHON = value
159
            
160
    
398 by Martin Pool
- testbzr finds the right version of bzr to test
161
    mypath = os.path.abspath(sys.argv[0])
413 by Martin Pool
- more indicators at top of test output
162
    print '%-30s %s' % ('running tests from', mypath)
398 by Martin Pool
- testbzr finds the right version of bzr to test
163
164
    global BZRPATH
165
507 by Martin Pool
- new -p option for testbzr to use a different version of python
166
    if args:
623 by Martin Pool
- fix invocation of testbzr when giving explicit bzr location
167
        BZRPATH = args[0]
398 by Martin Pool
- testbzr finds the right version of bzr to test
168
    else:
169
        BZRPATH = os.path.join(os.path.split(mypath)[0], 'bzr')
170
413 by Martin Pool
- more indicators at top of test output
171
    print '%-30s %s' % ('against bzr', BZRPATH)
172
    print '%-30s %s' % ('in directory', os.getcwd())
507 by Martin Pool
- new -p option for testbzr to use a different version of python
173
    print '%-30s %s' % ('with python', (OVERRIDE_PYTHON or '(default)'))
398 by Martin Pool
- testbzr finds the right version of bzr to test
174
    print
624 by Martin Pool
- make sure bzr is always explicitly invoked through
175
    print backtick('bzr version')
398 by Martin Pool
- testbzr finds the right version of bzr to test
176
    
300 by Martin Pool
- more tests
177
    runcmd(['mkdir', TESTDIR])
299 by Martin Pool
testbzr:
178
    cd(TESTDIR)
731 by Martin Pool
- merge plugin patch from john
179
    # This means that any command that is naively run in this directory
180
    # Won't affect the parent directory.
181
    runcmd('bzr init')
456 by Martin Pool
- tests for bzr root
182
    test_root = os.getcwd()
296 by Martin Pool
- better reports from testbzr when it fails
183
300 by Martin Pool
- more tests
184
    progress("introductory commands")
296 by Martin Pool
- better reports from testbzr when it fails
185
    runcmd("bzr version")
335 by Martin Pool
- add new failing test for command parsing
186
    runcmd("bzr --version")
296 by Martin Pool
- better reports from testbzr when it fails
187
    runcmd("bzr help")
297 by Martin Pool
- fix intentional testcase failure
188
    runcmd("bzr --help")
189
399 by Martin Pool
- testbzr also runs selftests
190
    progress("internal tests")
191
    runcmd("bzr selftest")
192
300 by Martin Pool
- more tests
193
    progress("invalid commands")
298 by Martin Pool
- test some commands known to fail
194
    runcmd("bzr pants", retcode=1)
195
    runcmd("bzr --pants off", retcode=1)
382 by Martin Pool
- test previous commit
196
    runcmd("bzr diff --message foo", retcode=1)
296 by Martin Pool
- better reports from testbzr when it fails
197
300 by Martin Pool
- more tests
198
    progress("basic branch creation")
199
    runcmd(['mkdir', 'branch1'])
200
    cd('branch1')
201
    runcmd('bzr init')
303 by Martin Pool
- more tests for unknown file
202
456 by Martin Pool
- tests for bzr root
203
    assert backtick('bzr root')[:-1] == os.path.join(test_root, 'branch1')
204
303 by Martin Pool
- more tests for unknown file
205
    progress("status of new file")
300 by Martin Pool
- more tests
206
    
207
    f = file('test.txt', 'wt')
208
    f.write('hello world!\n')
209
    f.close()
210
307 by Martin Pool
testbzr: clean up crlf handling
211
    out = backtick("bzr unknowns")
212
    assert out == 'test.txt\n'
303 by Martin Pool
- more tests for unknown file
213
307 by Martin Pool
testbzr: clean up crlf handling
214
    out = backtick("bzr status")
477 by Martin Pool
- fix header for listing of unknown files
215
    assert out == 'unknown:\n  test.txt\n'
303 by Martin Pool
- more tests for unknown file
216
307 by Martin Pool
testbzr: clean up crlf handling
217
    out = backtick("bzr status --all")
477 by Martin Pool
- fix header for listing of unknown files
218
    assert out == "unknown:\n  test.txt\n"
304 by Martin Pool
testbzr: test adding a file
219
404 by Martin Pool
- bzr status now optionally takes filenames to check
220
    out = backtick("bzr status test.txt --all")
477 by Martin Pool
- fix header for listing of unknown files
221
    assert out == "unknown:\n  test.txt\n"
404 by Martin Pool
- bzr status now optionally takes filenames to check
222
223
    f = file('test2.txt', 'wt')
224
    f.write('goodbye cruel world...\n')
225
    f.close()
226
227
    out = backtick("bzr status test.txt")
480 by Martin Pool
- more status form test fixes
228
    assert out == "unknown:\n  test.txt\n"
404 by Martin Pool
- bzr status now optionally takes filenames to check
229
230
    out = backtick("bzr status")
480 by Martin Pool
- more status form test fixes
231
    assert out == ("unknown:\n"
232
                   "  test.txt\n"
233
                   "  test2.txt\n")
404 by Martin Pool
- bzr status now optionally takes filenames to check
234
235
    os.unlink('test2.txt')
236
350 by Martin Pool
- refactor command aliases into command classes
237
    progress("command aliases")
238
    out = backtick("bzr st --all")
482 by Martin Pool
- more status form test fixes
239
    assert out == ("unknown:\n"
240
                   "  test.txt\n")
241
    
350 by Martin Pool
- refactor command aliases into command classes
242
    out = backtick("bzr stat")
482 by Martin Pool
- more status form test fixes
243
    assert out == ("unknown:\n"
244
                   "  test.txt\n")
350 by Martin Pool
- refactor command aliases into command classes
245
246
    progress("command help")
247
    runcmd("bzr help st")
248
    runcmd("bzr help")
249
    runcmd("bzr help commands")
352 by Martin Pool
- Show aliases in command help
250
    runcmd("bzr help slartibartfast", 1)
251
252
    out = backtick("bzr help ci")
253
    out.index('aliases: ')
350 by Martin Pool
- refactor command aliases into command classes
254
305 by Martin Pool
testbzr: test renames
255
    progress("can't rename unversioned file")
256
    runcmd("bzr rename test.txt new-test.txt", 1)
257
304 by Martin Pool
testbzr: test adding a file
258
    progress("adding a file")
259
260
    runcmd("bzr add test.txt")
261
    assert backtick("bzr unknowns") == ''
482 by Martin Pool
- more status form test fixes
262
    assert backtick("bzr status --all") == ("added:\n"
263
                                            "  test.txt\n")
300 by Martin Pool
- more tests
264
305 by Martin Pool
testbzr: test renames
265
    progress("rename newly-added file")
266
    runcmd("bzr rename test.txt hello.txt")
306 by Martin Pool
testbzr: test renames
267
    assert os.path.exists("hello.txt")
268
    assert not os.path.exists("test.txt")
305 by Martin Pool
testbzr: test renames
269
308 by Martin Pool
fix test suite
270
    assert backtick("bzr revno") == '0\n'
307 by Martin Pool
testbzr: clean up crlf handling
271
339 by Martin Pool
many more diffs
272
    progress("add first revision")
273
    runcmd(["bzr", "commit", "-m", 'add first revision'])
274
275
    progress("more complex renames")
276
    os.mkdir("sub1")
277
    runcmd("bzr rename hello.txt sub1", 1)
278
    runcmd("bzr rename hello.txt sub1/hello.txt", 1)
279
    runcmd("bzr move hello.txt sub1", 1)
280
281
    runcmd("bzr add sub1")
282
    runcmd("bzr rename sub1 sub2")
283
    runcmd("bzr move hello.txt sub2")
560 by Martin Pool
- fix testbzr for win32
284
    assert backtick("bzr relpath sub2/hello.txt") == os.path.join("sub2", "hello.txt\n")
339 by Martin Pool
many more diffs
285
286
    assert exists("sub2")
287
    assert exists("sub2/hello.txt")
288
    assert not exists("sub1")
289
    assert not exists("hello.txt")
290
291
    runcmd(['bzr', 'commit', '-m', 'commit with some things moved to subdirs'])
292
293
    mkdir("sub1")
294
    runcmd('bzr add sub1')
295
    runcmd('bzr move sub2/hello.txt sub1')
296
    assert not exists('sub2/hello.txt')
297
    assert exists('sub1/hello.txt')
298
    runcmd('bzr move sub2 sub1')
299
    assert not exists('sub2')
300
    assert exists('sub1/sub2')
301
302
    runcmd(['bzr', 'commit', '-m', 'rename nested subdirectories'])
303
304
    cd('sub1/sub2')
456 by Martin Pool
- tests for bzr root
305
    assert backtick('bzr root')[:-1] == os.path.join(test_root, 'branch1')
339 by Martin Pool
many more diffs
306
    runcmd('bzr move ../hello.txt .')
340 by Martin Pool
- more testcase fixes
307
    assert exists('./hello.txt')
560 by Martin Pool
- fix testbzr for win32
308
    assert backtick('bzr relpath hello.txt') == os.path.join('sub1', 'sub2', 'hello.txt\n')
309
    assert backtick('bzr relpath ../../sub1/sub2/hello.txt') == os.path.join('sub1', 'sub2', 'hello.txt\n')
339 by Martin Pool
many more diffs
310
    runcmd(['bzr', 'commit', '-m', 'move to parent directory'])
311
    cd('..')
560 by Martin Pool
- fix testbzr for win32
312
    assert backtick('bzr relpath sub2/hello.txt') == os.path.join('sub1', 'sub2', 'hello.txt\n')
340 by Martin Pool
- more testcase fixes
313
314
    runcmd('bzr move sub2/hello.txt .')
315
    assert exists('hello.txt')
389 by Martin Pool
- new commit --file option!
316
317
    f = file('hello.txt', 'wt')
318
    f.write('some nice new content\n')
319
    f.close()
320
321
    f = file('msg.tmp', 'wt')
322
    f.write('this is my new commit\n')
323
    f.close()
324
325
    runcmd('bzr commit -F msg.tmp')
394 by Martin Pool
- Fix argument handling in export command
326
327
    assert backtick('bzr revno') == '5\n'
328
    runcmd('bzr export -r 5 export-5.tmp')
329
    runcmd('bzr export export.tmp')
461 by Martin Pool
- remove compare_inventories() in favor of compare_trees()
330
331
    runcmd('bzr log')
332
    runcmd('bzr log -v')
511 by Martin Pool
- add tests for files and directories with spaces in name
333
334
335
336
    progress("file with spaces in name")
337
    mkdir('sub directory')
338
    file('sub directory/file with spaces ', 'wt').write('see how this works\n')
339
    runcmd('bzr add .')
340
    runcmd('bzr diff')
341
    runcmd('bzr commit -m add-spaces')
342
    runcmd('bzr check')
343
545 by Martin Pool
- --forward option for log
344
    runcmd('bzr log')
345
    runcmd('bzr log --forward')
346
541 by Martin Pool
- add lazy test for 'bzr info'
347
    runcmd('bzr info')
561 by Martin Pool
- add failing test for problem with stat after remove,
348
349
595 by Martin Pool
- tests for add --no-recurse
350
    
351
352
561 by Martin Pool
- add failing test for problem with stat after remove,
353
354
    cd('..')
355
    cd('..')
628 by Martin Pool
- merge aaron's updated merge/pull code
356
    progress('branch')
357
    # Can't create a branch if it already exists
358
    runcmd('bzr branch branch1', retcode=1)
359
    # Can't create a branch if its parent doesn't exist
360
    runcmd('bzr branch /unlikely/to/exist', retcode=1)
361
    runcmd('bzr branch branch1 branch2')
362
363
    progress("pull")
364
    cd('branch1')
365
    runcmd('bzr pull', retcode=1)
366
    runcmd('bzr pull ../branch2')
367
    cd('.bzr')
368
    runcmd('bzr pull')
369
    runcmd('bzr commit -m empty')
370
    runcmd('bzr pull')
371
    cd('../../branch2')
372
    runcmd('bzr pull')
373
    runcmd('bzr commit -m empty')
374
    cd('../branch1')
375
    runcmd('bzr commit -m empty')
376
    runcmd('bzr pull', retcode=1)
377
    cd ('..')
561 by Martin Pool
- add failing test for problem with stat after remove,
378
379
    progress('status after remove')
380
    mkdir('status-after-remove')
381
    # see mail from William Dodé, 2005-05-25
382
    # $ bzr init; touch a; bzr add a; bzr commit -m "add a"
383
    #     * looking for changes...
384
    #     added a
385
    #     * commited r1
386
    #     $ bzr remove a
387
    #     $ bzr status
388
    #     bzr: local variable 'kind' referenced before assignment
389
    #     at /vrac/python/bazaar-ng/bzrlib/diff.py:286 in compare_trees()
390
    #     see ~/.bzr.log for debug information
391
    cd('status-after-remove')
392
    runcmd('bzr init')
393
    file('a', 'w').write('foo')
394
    runcmd('bzr add a')
395
    runcmd(['bzr', 'commit', '-m', 'add a'])
396
    runcmd('bzr remove a')
397
    runcmd('bzr status')
398
410 by Martin Pool
- Fix ignore command and add tests
399
    cd('..')
400
401
    progress('ignore patterns')
402
    mkdir('ignorebranch')
403
    cd('ignorebranch')
404
    runcmd('bzr init')
405
    assert backtick('bzr unknowns') == ''
406
407
    file('foo.tmp', 'wt').write('tmp files are ignored')
408
    assert backtick('bzr unknowns') == ''
409
410
    file('foo.c', 'wt').write('int main() {}')
411
    assert backtick('bzr unknowns') == 'foo.c\n'
412
    runcmd('bzr add foo.c')
413
    assert backtick('bzr unknowns') == ''
414
499 by Martin Pool
- new bzr ignore test cases from ddaa
415
    # 'ignore' works when creating the .bzignore file
410 by Martin Pool
- Fix ignore command and add tests
416
    file('foo.blah', 'wt').write('blah')
417
    assert backtick('bzr unknowns') == 'foo.blah\n'
418
    runcmd('bzr ignore *.blah')
419
    assert backtick('bzr unknowns') == ''
499 by Martin Pool
- new bzr ignore test cases from ddaa
420
    assert file('.bzrignore', 'rb').read() == '*.blah\n'
410 by Martin Pool
- Fix ignore command and add tests
421
499 by Martin Pool
- new bzr ignore test cases from ddaa
422
    # 'ignore' works when then .bzrignore file already exists
423
    file('garh', 'wt').write('garh')
424
    assert backtick('bzr unknowns') == 'garh\n'
425
    runcmd('bzr ignore garh')
426
    assert backtick('bzr unknowns') == ''
427
    assert file('.bzrignore', 'rb').read() == '*.blah\ngarh\n'
300 by Martin Pool
- more tests
428
595 by Martin Pool
- tests for add --no-recurse
429
    cd('..')
430
431
432
433
434
    progress("recursive and non-recursive add")
435
    mkdir('no-recurse')
436
    cd('no-recurse')
437
    runcmd('bzr init')
438
    mkdir('foo')
439
    fp = os.path.join('foo', 'test.txt')
440
    f = file(fp, 'w')
441
    f.write('hello!\n')
442
    f.close()
443
    runcmd('bzr add --no-recurse foo')
444
    runcmd('bzr file-id foo')
445
    runcmd('bzr file-id ' + fp, 1)      # not versioned yet
446
    runcmd('bzr commit -m add-dir-only')
447
448
    runcmd('bzr file-id ' + fp, 1)      # still not versioned 
449
450
    runcmd('bzr add foo')
451
    runcmd('bzr file-id ' + fp)
452
    runcmd('bzr commit -m add-sub-file')
453
    
454
    cd('..')
455
456
511 by Martin Pool
- add tests for files and directories with spaces in name
457
458
641 by Martin Pool
- improved external-command patch from john
459
    # Run any function in this 
460
    g = globals()
461
    funcs = g.keys()
462
    funcs.sort()
463
    for k in funcs:
464
        if k.startswith('test_') and callable(g[k]):
465
            progress(k[5:].replace('_', ' '))
466
            g[k]()
467
296 by Martin Pool
- better reports from testbzr when it fails
468
    progress("all tests passed!")
469
except Exception, e:
470
    sys.stderr.write('*' * 50 + '\n'
471
                     + 'testbzr: tests failed\n'
335 by Martin Pool
- add new failing test for command parsing
472
                     + 'see ' + LOGFILENAME + ' for more information\n'
296 by Martin Pool
- better reports from testbzr when it fails
473
                     + '*' * 50 + '\n')
474
    logfile.write('tests failed!\n')
475
    traceback.print_exc(None, logfile)
513 by Martin Pool
- show some log output if the tests fail
476
    logfile.close()
477
478
    sys.stdout.writelines(file(os.path.join(start_dir, LOGFILENAME), 'rt').readlines()[-50:])
479
    
296 by Martin Pool
- better reports from testbzr when it fails
480
    sys.exit(1)
513 by Martin Pool
- show some log output if the tests fail
481