smarbs 0.9.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/test/test_log.rb ADDED
@@ -0,0 +1,128 @@
1
+ $:.unshift File.join(File.dirname(__FILE__),'..','lib')
2
+ $:.unshift File.join(File.dirname(__FILE__),'..','test')
3
+
4
+ require 'script'
5
+ require 'smarbs_test'
6
+
7
+ class Net::SMTP
8
+
9
+ remove_method(:start)
10
+
11
+ def self.a
12
+ @@a
13
+ end
14
+
15
+ def send_message (a, b, c)
16
+ @@a = a
17
+ end
18
+
19
+ def start (a=1, b=1, c=1, d=1, e=1)
20
+ return yield(self)
21
+ end
22
+ end
23
+
24
+ class TestLog < Test::Unit::TestCase;
25
+ include SmarbsTest
26
+
27
+ def setup
28
+ clean
29
+ end
30
+
31
+ def teardown
32
+ clean_all
33
+ end
34
+
35
+ def test_checks
36
+ cf = default_configfile
37
+ cf.sendmail = true
38
+ cf.testm = false
39
+ cf.successmail = false
40
+ cf.minbackuplevels = 1
41
+ cf.write
42
+ assert_fail "The following error occured: E-mail provided in Configfile is wrong or empty!" do
43
+ Script.new("/tmp/smarbs/smarbs")
44
+ end
45
+
46
+ cf.email = "rggjan@gmail.com"
47
+ cf.testm=true
48
+ cf.write
49
+ assert_equal(true, cf.testm)
50
+ assert_success "testm has been automatically set to 'no'." do
51
+ Script.new("/tmp/smarbs/smarbs")
52
+ end
53
+
54
+ cf = ConfigFile.new("/tmp/smarbs/smarbs/backup1")
55
+ assert_equal(false, cf.testm)
56
+ assert_equal("From: smarbs <rggjan@gmail.com>
57
+ To: smarbs Admin <rggjan@gmail.com>
58
+ Subject: Test email, backup1
59
+ This test email was sent to check the smtp-connection of the configfile \"backup1\".
60
+ The \"testm\" option in the script should have been automatically disabled by now,
61
+ so you wont receive test emails anymore.
62
+
63
+ ---
64
+
65
+ For any questions concerning smarbs, you can contact me here: rggjan@gmail.com", Net::SMTP.a)
66
+ cf.limit = Size.new(0)
67
+ cf.write
68
+ assert_fail("The following error occured: Not enough space available to do an incremental backup!") do
69
+ Script.new("/tmp/smarbs/smarbs")
70
+ end
71
+ m="Backup NOT successful!
72
+ The following error occured: Not enough space available to do an incremental backup!
73
+ Check your configfile /tmp/smarbs/smarbs/backup1.
74
+
75
+
76
+ ---
77
+
78
+ For any questions concerning smarbs, you can contact me here: rggjan@gmail.com"
79
+ assert_equal(m, Net::SMTP.a[-m.size, m.size])
80
+
81
+ cf.limit = Size.new("20m")
82
+ cf.successmail = true
83
+ cf.write
84
+ assert_success do
85
+ Script.new("/tmp/smarbs/smarbs")
86
+ end
87
+ assert_equal("From: smarbs <rggjan@gmail.com>
88
+ To: smarbs Admin <rggjan@gmail.com>
89
+ Subject: Smarbs, backup \"backup1\" successful
90
+ Smarbs backup ended with the following message:
91
+ Successfully backed up.
92
+
93
+ Here is the full logfile:\n\n", Net::SMTP.a[0,213])
94
+
95
+ cf.uname = "test"
96
+ cf.tls = true
97
+ cf.write
98
+ assert_fail "The following error occured: Both or none of the uname and the passwd must be specified!" do
99
+ Script.new("/tmp/smarbs/smarbs")
100
+ end
101
+
102
+ cf.passwd = "test"
103
+ cf.write
104
+ assert_success do
105
+ Script.new("/tmp/smarbs/smarbs")
106
+ end
107
+
108
+ cf.logdir = "/tmp/smarbs/logdir/"
109
+ cf.write
110
+ assert_success do
111
+ Script.new("/tmp/smarbs/smarbs")
112
+ end
113
+
114
+
115
+ log = `ls -d /tmp/smarbs/logdir/*`.strip
116
+ assert_equal("Working on backup1\n", File.new(log, "r").readlines[0])
117
+
118
+ m=": Successfully backed up.
119
+
120
+
121
+
122
+ ---
123
+
124
+ For any questions concerning smarbs, you can contact me here: rggjan@gmail.com"
125
+
126
+ assert_equal(m, Net::SMTP.a[-m.size, m.size])
127
+ end
128
+ end
@@ -0,0 +1,118 @@
1
+ $:.unshift File.join(File.dirname(__FILE__),'..','lib')
2
+ $:.unshift File.join(File.dirname(__FILE__),'..','test')
3
+
4
+ require 'test/unit'
5
+ require 'stringio'
6
+ require 'script'
7
+ require 'backup'
8
+ require 'smarbs_test'
9
+
10
+ class TestScript < Test::Unit::TestCase;
11
+ include SmarbsTest
12
+
13
+ def setup
14
+ clean
15
+ end
16
+
17
+ def teardown
18
+ clean_all
19
+ end
20
+
21
+ def test_init
22
+ assert_output("Config file created, edit /tmp/smarbs/smarbs/backup1!\n") do
23
+ Script.new("/tmp/smarbs/smarbs/")
24
+ end
25
+
26
+ c = ConfigFile.new("/tmp/smarbs/smarbs/backup1")
27
+ c.src=["/tmp/smarbs/backmeup/first", "/tmp/smarbs/backmeup/second"]
28
+ c.dest=("/tmp/smarbs/backmeup")
29
+ c.write
30
+ `mkdir -p /tmp/smarbs/backmeup/first`
31
+ `mkdir -p /tmp/smarbs/backmeup/second`
32
+ `mkdir -p /tmp/smarbs/backedup/smarbsbackup/1342-backup.1/first`
33
+ `mkdir -p /tmp/smarbs/backedup/smarbsbackup/1342-backup.1/second`
34
+ `echo "lskdj v0.9" > /tmp/smarbs/smarbs/home`
35
+ `echo "vir v0.9" > /tmp/smarbs/smarbs/virtualbox`
36
+ 3.times do
37
+ assert_output("Successfully backed up.") do
38
+ Script.new("/tmp/smarbs/smarbs", ["backup1"])
39
+ end
40
+ end
41
+
42
+ assert_output("Error: Directory '/etc/non_existing_dir/' is not existing!") do
43
+ Script.new("/etc/non_existing_dir/")
44
+ end
45
+
46
+ a=nil
47
+ assert_output(["Available configfiles:", "virtualbox", "home", "backup1"]) do
48
+ a=Script.new("/tmp/smarbs/smarbs", ["--list"])
49
+ end
50
+
51
+ assert_output(["Information options:", "ABSOLUTELY NO WARRANTY", "if not executed as root."]) do
52
+ a.parse("--help".split)
53
+ end
54
+
55
+ assert_output("Unknown argument '--Version'!\nUse '--help' to see possible options.\n", :exact => true) do
56
+ a.parse("--Version --help".split)
57
+ end
58
+
59
+ assert_output("Unknown argument 'notthere'!\nUse '--help' to see possible options.\n", :exact => true) do
60
+ a.parse("--help notthere".split)
61
+ end
62
+
63
+ assert_output("You cannot backup and use '--help' at the same time.\n", :exact => true) do
64
+ a.parse("home --help".split)
65
+ end
66
+
67
+ assert_output("You cannot backup and use '-l' at the same time.\n", :exact => true) do
68
+ a.parse("-l home --help".split)
69
+ end
70
+
71
+ assert_output("smarbs 0.9.1 (4. September 2009)\nCopyright (C) 2006-2009 by Jan Rueegg (rggjan)\n<http://smarbs.sourceforge.net/>\n\nsmarbs comes with ABSOLUTELY NO WARRANTY. This is free software, and you\nare welcome to redistribute it under certain conditions. See the GNU\nGeneral Public Licence for details.\n", :exact) do
72
+ a.parse("-V".split)
73
+ end
74
+
75
+ assert_output("Error: Configfile /tmp/smarbs/smarbs/home is invalid. Please delete it!\nCheck your configfile /tmp/smarbs/smarbs/home.\nError: Configfile /tmp/smarbs/smarbs/virtualbox is invalid. Please delete it!\nCheck your configfile /tmp/smarbs/smarbs/virtualbox.\n") do
76
+ a.parse("home virtualbox".split)
77
+ end
78
+
79
+ `echo "-preferences-\n--end of preferences--" > /tmp/smarbs/smarbs/home`
80
+ `echo "-preferences-\n--end of preferences--" > /tmp/smarbs/smarbs/virtualbox`
81
+ assert_fail("Source directory/file /directory/to/source1 not existing!") do
82
+ a.parse(["home"])
83
+ end
84
+
85
+ assert_fail("Source directory/file /directory/to/source1 not existing!") do
86
+ a.parse("home --pass-rsync=".split)
87
+ end
88
+
89
+ assert_fail("Source directory/file /directory/to/source1 not existing!") do
90
+ a.parse("--pass-rsync=lkjlk -v --verbose home".split)
91
+ end
92
+
93
+ assert_fail("Source directory/file /directory/to/source1 not existing!") do
94
+ a.parse("--pass-rsync=lkjlk --verbose --pass-rsync=ooo home".split)
95
+ end
96
+
97
+ assert_output("You cannot backup and use '--list' at the same time.\n") do
98
+ a.parse("--list home --help".split)
99
+ end
100
+
101
+ assert_output("Unknown argument 'home2'!\nUse '--help' to see possible options.\n") do
102
+ a.parse("-l home2 -lp --pass-rsync=".split)
103
+ end
104
+
105
+ assert_output("Source directory/file /directory/to/source1 not existing!") do
106
+ a.parse("--verbose home".split)
107
+ end
108
+ end
109
+
110
+ def test_no_limit_when_ownpartition
111
+ assert_output(["Limit:", "ignored", "ownpartition is set"]) do
112
+ cf = default_configfile
113
+ cf.ownpartition = true
114
+ cf.write
115
+ Script.new("/tmp/smarbs/smarbs/")
116
+ end
117
+ end
118
+ end
@@ -0,0 +1,131 @@
1
+ $:.unshift File.join(File.dirname(__FILE__),'..','lib')
2
+ $:.unshift File.join(File.dirname(__FILE__),'..','test')
3
+
4
+ require 'script'
5
+ require 'backup'
6
+ require 'smarbs_test'
7
+
8
+ class TestSmarbs < Test::Unit::TestCase;
9
+ include SmarbsTest
10
+
11
+ def setup
12
+ clean
13
+ end
14
+
15
+ def teardown
16
+ clean_all
17
+ end
18
+
19
+ def test_init
20
+ `rm -r /tmp/smarbs/smarbs/`
21
+ assert_output("/tmp/smarbs/smarbs/ created.") do
22
+ Script.new("/tmp/smarbs/smarbs", [])
23
+ end
24
+
25
+ assert_fail("Source directory/file /directory/to/source1 not existing") do
26
+ Script.new("/tmp/smarbs/smarbs")
27
+ end
28
+
29
+ cf = ConfigFile.new("/tmp/smarbs/smarbs/backup1")
30
+ cf.src = ["/tmp/smarbs/backmeup"]
31
+ cf.dest = "/tmp/smarbs/backedup/"
32
+ cf.write
33
+
34
+ assert_success do
35
+ Script.new("/tmp/smarbs/smarbs")
36
+ end
37
+ assert_backup_dirs([1])
38
+
39
+ assert_success do
40
+ Script.new("/tmp/smarbs/smarbs")
41
+ end
42
+ assert_backup_dirs([1, 2])
43
+
44
+ assert_success do
45
+ Script.new("/tmp/smarbs/smarbs")
46
+ end
47
+ assert_backup_dirs([1, 2, 3])
48
+
49
+ assert_success do
50
+ Script.new("/tmp/smarbs/smarbs")
51
+ end
52
+ assert_backup_dirs([1, 2, 3, 4])
53
+
54
+ cf.limit = Size.new(10)
55
+ cf.write
56
+ assert_fail("Not enough space available, next deletion would go below your minbackuplevels of 5!") do
57
+ Script.new("/tmp/smarbs/smarbs")
58
+ end
59
+
60
+ cf.minbackuplevels = 2
61
+ cf.write
62
+ assert_fail(["...removing backup.3", "...removing backup.4...",
63
+ "Not enough space available, next deletion would go below your minbackuplevels of 2!"]) do
64
+ Script.new("/tmp/smarbs/smarbs")
65
+ end
66
+
67
+ cf.minbackuplevels = 1
68
+ cf.write
69
+ assert_fail("Not enough space available to do an incremental backup!") do
70
+ Script.new("/tmp/smarbs/smarbs")
71
+ end
72
+
73
+ cf.limit=(Size.new("10m"))
74
+ cf.write
75
+ `mkdir /tmp/smarbs/backmeup/test`
76
+ `touch /tmp/smarbs/backmeup/test2`
77
+ assert_success do
78
+ Script.new("/tmp/smarbs/smarbs")
79
+ end
80
+ assert_equal("test\ntest2\n", `ls /tmp/smarbs/backedup/smarbsbackup/*.1/backmeup`)
81
+
82
+ cf.exclude=["test"]
83
+ cf.write
84
+ 2.times do
85
+ assert_success do
86
+ Script.new("/tmp/smarbs/smarbs")
87
+ end
88
+ end
89
+ assert_equal("test2\n", `ls /tmp/smarbs/backedup/smarbsbackup/*.1/backmeup`)
90
+
91
+ # Test maxbackuplevels
92
+ 2.times do
93
+ assert_success do
94
+ Script.new("/tmp/smarbs/smarbs")
95
+ end
96
+ end
97
+ assert_equal("test2\n", `ls /tmp/smarbs/backedup/smarbsbackup/*.1/backmeup`)
98
+ assert_backup_dirs([1, 2, 3, 4, 5, 6])
99
+
100
+ cf.maxbackuplevels = 4
101
+ cf.write
102
+ assert_success ["Deleting old backups...", "maxbackuplevels: 4", "backups: 6", "...removing backup.3..."] do
103
+ Script.new("/tmp/smarbs/smarbs")
104
+ end
105
+ assert_backup_dirs([1, 2, 3, 5])
106
+
107
+ cf.minbackuplevels=5
108
+ cf.write
109
+ assert_fail "Not enough space available, next deletion would go below your minbackuplevels of 5!" do
110
+ Script.new("/tmp/smarbs/smarbs")
111
+ end
112
+ assert_backup_dirs([1, 2, 3, 5])
113
+
114
+ cf.maxbackuplevels=0
115
+ cf.minbackuplevels=1
116
+ cf.write
117
+ 3.times do
118
+ assert_success do
119
+ Script.new("/tmp/smarbs/smarbs")
120
+ end
121
+ end
122
+ assert_backup_dirs([1, 2, 3, 4, 5, 6, 8])
123
+
124
+ cf.maxbackuplevels=3
125
+ cf.write
126
+ assert_success do
127
+ Script.new("/tmp/smarbs/smarbs")
128
+ end
129
+ assert_backup_dirs([1, 2, 3])
130
+ end
131
+ end
@@ -0,0 +1,76 @@
1
+ $:.unshift File.join(File.dirname(__FILE__),'..','lib')
2
+ $:.unshift File.join(File.dirname(__FILE__),'..','test')
3
+
4
+ require 'test/unit'
5
+ require 'smarbs_test'
6
+ require 'types'
7
+
8
+ class TestSmarbsTest < Test::Unit::TestCase
9
+ include SmarbsTest
10
+
11
+ def test_array
12
+ assert_equal 2, ([:a, :b, :c, :d].index{|item| item == :c})
13
+ assert_equal nil, ([:a, :b, :c, :d].index{|item| item == :e})
14
+ end
15
+
16
+ def test_constants
17
+ assert_equal "0.9.1", SMARBS::VERSION
18
+ assert_equal "4. September 2009", SMARBS::DATE
19
+ assert File.file?(SMARBS::DATADIR + "/icongreen.png")
20
+ end
21
+
22
+ def test_clean
23
+ `touch /tmp/smarbs`
24
+ clean
25
+ assert File.directory?("/tmp/smarbs")
26
+
27
+ `mkdir -p /tmp/smarbs/dir/dir2 && touch /tmp/smarbs/dir/file`
28
+ clean
29
+ assert_equal ["backedup", "backmeup", "smarbs"], Directory.new("/tmp/smarbs").files(true).sort
30
+ end
31
+
32
+ def test_clean_all
33
+ `touch /tmp/smarbs`
34
+ clean_all
35
+ assert !File.directory?("/tmp/smarbs")
36
+ end
37
+
38
+
39
+ def test_assert_output
40
+ assert_output("word1 word2 word3") do
41
+ puts "word1 word2 word3"
42
+ end
43
+
44
+ assert_output("word1 word2 word3\n", :exact) do
45
+ puts "word1 word2 word3"
46
+ end
47
+
48
+ assert_output(/word2/) do
49
+ puts "word1 word2 word3"
50
+ end
51
+
52
+ assert_output("word2") do
53
+ puts "word1 word2 word3"
54
+ end
55
+
56
+ assert_output("word2", :nonequal) do
57
+ puts "word1 word6 word3"
58
+ end
59
+
60
+ assert_output("word2", :nonequal, :exact) do
61
+ puts "word2 "
62
+ end
63
+ end
64
+
65
+ def test_assert_success
66
+ assert_success do
67
+ puts "abc\nSuccessfully backed up.\ndef"
68
+ end
69
+
70
+ assert_raise MiniTest::Assertion do
71
+ assert_success do
72
+ puts "abc\n\ndef"
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,11 @@
1
+ $:.unshift File.join(File.dirname(__FILE__),'..','test')
2
+
3
+ require 'test/unit'
4
+
5
+ require 'test_backup'
6
+ require 'test_log'
7
+ require 'test_script'
8
+ require 'test_smarbs'
9
+ require 'test_smarbs_test'
10
+ require 'test_types'
11
+ require 'test_helpers'
@@ -0,0 +1,141 @@
1
+ $:.unshift File.join(File.dirname(__FILE__),'..','lib')
2
+ $:.unshift File.join(File.dirname(__FILE__),'..','test')
3
+
4
+ require 'types'
5
+ require 'helpers'
6
+ require 'test/unit'
7
+ require 'stringio'
8
+ require 'smarbs_test'
9
+
10
+ class TestTypes < Test::Unit::TestCase;
11
+ include SmarbsTest
12
+
13
+ def setup
14
+ clean
15
+ end
16
+
17
+ def teardown
18
+ clean
19
+ end
20
+
21
+ def test_directory
22
+
23
+ assert_output("/tmp/smarbs/dir1/ created.\n", :exact => true) do
24
+ Directory.new("/tmp/smarbs/dir1/")
25
+ end
26
+
27
+ assert_output("/tmp/smarbs/dir1/dir2/dir3/ created.\n", :exact => true) do
28
+ Directory.new("/tmp/smarbs/dir1/dir2/dir3")
29
+ end
30
+
31
+ assert_output("", :exact => true) do
32
+ Directory.new("/tmp/smarbs/dir1/dir2")
33
+ end
34
+
35
+ assert_raise_message("/etc/smarbs/ is not writable!") { Directory.new("/etc/smarbs", :writable) }
36
+ assert_raise_message( "No directory specified!" ) { Directory.new("") }
37
+ assert_raise_message( "/etc/ is not writable!" ) { Directory.new("etc", :writable)}
38
+ assert_raise_message( "Directory '/etc/uiae/' is not existing!" ) { Directory.new("/etc/uiae")}
39
+
40
+ assert_output("", :exact => true) do
41
+ Directory.new("/etc/")
42
+ end
43
+
44
+ assert_output("/tmp/smarbs/home/jan/ created.") do
45
+ Directory.new("/tmp/smarbs/home/jan", :writable)
46
+ end
47
+
48
+ assert_equal( Directory.new(" /tmp/smarbs/home/jan"),
49
+ Directory.new("/tmp/smarbs/home/jan/ ") )
50
+ assert_equal( Directory.new("/tmp/smarbs/home/jan"),
51
+ Directory.new("tmp/smarbs/home/jan") )
52
+ assert_equal( Directory.new("/tmp/smarbs/home/jan/").to_s,
53
+ Directory.new("tmp/smarbs/home/jan").to_s )
54
+ assert_equal( Directory.new("/tmp/smarbs/home/jan/").dir,
55
+ Directory.new("tmp/smarbs/home/jan").dir )
56
+
57
+ clean
58
+ assert_equal([], Directory.new("/tmp/smarbs/").files(false).sort)
59
+ `cd /tmp/smarbs/smarbs/ && touch maxtor && touch trekstor && touch mhome &&
60
+ touch mhome~ && touch maxtor~`
61
+ `mkdir /tmp/smarbs/smarbs/folder`
62
+ assert_equal(["maxtor", "trekstor", "mhome"].sort, Directory.new("/tmp/smarbs/smarbs").files(false).sort)
63
+ assert_equal(["maxtor", "trekstor", "mhome",
64
+ "folder", "mhome~", "maxtor~"].sort,
65
+ Directory.new("/tmp/smarbs/smarbs").files(true).sort)
66
+
67
+ assert_instance_of(Float, Directory.new("/tmp/smarbs/smarbs").free)
68
+
69
+ d=Directory.new("/tmp/smarbs/")
70
+ assert_kind_of Float, d.space(true)
71
+ assert_equal 20480, d.space
72
+ end
73
+
74
+ def test_sizes
75
+ a = Size.new("13g")
76
+ assert_equal(13958643712, a.b)
77
+ a = Size.new("13.2M")
78
+ assert_equal(13841203, a.b)
79
+ assert_equal("13.2m", a.short(-1))
80
+ a = Size.new("13.8")
81
+ assert_equal(14, a.b)
82
+ a = Size.new("0g")
83
+ assert_equal(0, a.b)
84
+ a = Size.new("13.44G")
85
+ assert_equal("13.44 GiB", a.opt)
86
+ a = Size.new("13.0m")
87
+ assert_equal("13.0 MiB", a.opt)
88
+ a = Size.new("10000")
89
+ assert_equal("9.77 KiB", a.opt)
90
+ a = Size.new("-13.8m")
91
+ assert_equal("-13.8 MiB", a.opt)
92
+ assert_equal("-13.8m", a.short)
93
+ assert_equal(-14470349, a.b)
94
+ assert_equal("-14470348.8", a.to_s)
95
+
96
+ zero = Size.new("")
97
+ assert_equal("0.0 B", zero.opt)
98
+ assert_equal("0.0", zero.short)
99
+
100
+ a = Size.new("44")
101
+ assert_equal("44.0 B", a.opt)
102
+ assert_equal("44.0", a.short)
103
+
104
+ a = Size.new("10000")
105
+ assert_equal("9.8 KiB", a.opt(1))
106
+ assert_equal("9.8k", a.short(1))
107
+ assert_equal("10.0k", a.short(0))
108
+ assert_equal("9.765625k", a.short(-1))
109
+
110
+ a = Size.new("2147483648")
111
+ assert_equal("2.0g", a.short(-1))
112
+ assert_equal("2.0g", a.short(2))
113
+
114
+ a = Size.new("2147483649")
115
+ assert_equal("2.0g", a.short(2))
116
+ assert_equal("2.00000000093132g", a.short(-1))
117
+
118
+ a = Size.new("2147483649")
119
+ assert_equal("2.0 GiB", a.opt(2))
120
+ assert_equal("2.00000000093132 GiB", a.opt(-1))
121
+
122
+ a = Size.new("-13.87678m")
123
+ assert_equal("-13.877 MiB", a.opt(3))
124
+
125
+ assert_equal Size.new("1k"), Size.new(1024)
126
+ assert_not_equal Size.new("1k"), Size.new("1m")
127
+
128
+ assert_equal Size.new(102).to_s, "102.0"
129
+
130
+ assert_raise( RuntimeError ) { Size.new("13q") }
131
+ assert_raise( RuntimeError ) { Size.new("13 q") }
132
+ assert_raise( RuntimeError ) { Size.new("m13") }
133
+ assert_raise( RuntimeError ) { Size.new("13mm") }
134
+ assert_raise( RuntimeError ) { Size.new("13gm") }
135
+ assert_raise( RuntimeError ) { Size.new("13 m m") }
136
+ assert_raise( RuntimeError ) { Size.new("13mm") }
137
+ assert_raise( RuntimeError ) { Size.new("13 g") }
138
+ assert_raise( RuntimeError ) { Size.new("k") }
139
+ end
140
+ end
141
+