1// Copyright (c) 2009, Google Inc.
2// All rights reserved.
3//
4// Redistribution and use in source and binary forms, with or without
5// modification, are permitted provided that the following conditions are
6// met:
7//
8//     * Redistributions of source code must retain the above copyright
9// notice, this list of conditions and the following disclaimer.
10//     * Redistributions in binary form must reproduce the above
11// copyright notice, this list of conditions and the following disclaimer
12// in the documentation and/or other materials provided with the
13// distribution.
14//     * Neither the name of Google Inc. nor the names of its
15// contributors may be used to endorse or promote products derived from
16// this software without specific prior written permission.
17//
18// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29//
30// BreakpadFramework_Test.mm
31// Test case file for Breakpad.h/mm.
32//
33
34#import "GTMSenTestCase.h"
35#import "Breakpad.h"
36
37#include <mach/mach.h>
38
39@interface BreakpadFramework_Test : GTMTestCase {
40 @private
41  int last_exception_code_;
42  int last_exception_type_;
43  mach_port_t last_exception_thread_;
44  // We're not using Obj-C BOOL because we need to interop with
45  // Breakpad's callback.
46  bool shouldHandleException_;
47}
48
49// This method is used by a callback used by test cases to determine
50// whether to return true or false to Breakpad when handling an
51// exception.
52- (bool)shouldHandleException;
53// This method returns a minimal dictionary that has what
54// Breakpad needs to initialize.
55- (NSMutableDictionary *)breakpadInitializationDictionary;
56// This method is used by the exception handling callback
57// to communicate to test cases the properites of the last
58// exception.
59- (void)setLastExceptionType:(int)type andCode:(int)code
60                   andThread:(mach_port_t)thread;
61@end
62
63// Callback for Breakpad exceptions
64bool myBreakpadCallback(int exception_type,
65                        int exception_code,
66                        mach_port_t crashing_thread,
67                        void *context);
68
69bool myBreakpadCallback(int exception_type,
70                        int exception_code,
71                        mach_port_t crashing_thread,
72                        void *context) {
73  BreakpadFramework_Test *testCaseClass =
74    (BreakpadFramework_Test *)context;
75  [testCaseClass setLastExceptionType:exception_type
76                              andCode:exception_code
77                            andThread:crashing_thread];
78  bool shouldHandleException =
79    [testCaseClass shouldHandleException];
80  NSLog(@"Callback returning %d", shouldHandleException);
81  return shouldHandleException;
82}
83const int kNoLastExceptionCode = -1;
84const int kNoLastExceptionType = -1;
85const mach_port_t kNoLastExceptionThread = MACH_PORT_NULL;
86
87@implementation BreakpadFramework_Test
88- (void) initializeExceptionStateVariables {
89  last_exception_code_ = kNoLastExceptionCode;
90  last_exception_type_ = kNoLastExceptionType;
91  last_exception_thread_ = kNoLastExceptionThread;
92}
93
94- (NSMutableDictionary *)breakpadInitializationDictionary {
95  NSMutableDictionary *breakpadParams =
96    [NSMutableDictionary dictionaryWithCapacity:3];
97
98  [breakpadParams setObject:@"UnitTests" forKey:@BREAKPAD_PRODUCT];
99  [breakpadParams setObject:@"1.0" forKey:@BREAKPAD_VERSION];
100  [breakpadParams setObject:@"http://staging" forKey:@BREAKPAD_URL];
101  return breakpadParams;
102}
103
104- (bool)shouldHandleException {
105  return shouldHandleException_;
106}
107
108- (void)setLastExceptionType:(int)type
109		     andCode:(int)code
110                   andThread:(mach_port_t)thread {
111  last_exception_type_ = type;
112  last_exception_code_ = code;
113  last_exception_thread_ = thread;
114}
115
116// Test that the parameters mark required actually enable Breakpad to
117// be initialized.
118- (void)testBreakpadInstantiationWithRequiredParameters {
119  BreakpadRef b = BreakpadCreate([self breakpadInitializationDictionary]);
120  STAssertNotNULL(b, @"BreakpadCreate failed with required parameters");
121  BreakpadRelease(b);
122}
123
124// Test that Breakpad fails to initialize cleanly when required
125// parameters are not present.
126- (void)testBreakpadInstantiationWithoutRequiredParameters {
127  NSMutableDictionary *breakpadDictionary =
128    [self breakpadInitializationDictionary];
129
130  // Skip setting version, so that BreakpadCreate fails.
131  [breakpadDictionary removeObjectForKey:@BREAKPAD_VERSION];
132  BreakpadRef b = BreakpadCreate(breakpadDictionary);
133  STAssertNULL(b, @"BreakpadCreate did not fail when missing a required"
134               " parameter!");
135
136  breakpadDictionary = [self breakpadInitializationDictionary];
137  // Now test with no product
138  [breakpadDictionary removeObjectForKey:@BREAKPAD_PRODUCT];
139  b = BreakpadCreate(breakpadDictionary);
140  STAssertNULL(b, @"BreakpadCreate did not fail when missing a required"
141               " parameter!");
142
143  breakpadDictionary = [self breakpadInitializationDictionary];
144  // Now test with no URL
145  [breakpadDictionary removeObjectForKey:@BREAKPAD_URL];
146  b = BreakpadCreate(breakpadDictionary);
147  STAssertNULL(b, @"BreakpadCreate did not fail when missing a required"
148               " parameter!");
149  BreakpadRelease(b);
150}
151
152// Test to ensure that when we call BreakpadAddUploadParameter,
153// it's added to the dictionary correctly(this test depends on
154// some internal details of Breakpad, namely, the special prefix
155// that it uses to figure out which key/value pairs to upload).
156- (void)testAddingBreakpadServerVariable {
157  NSMutableDictionary *breakpadDictionary =
158    [self breakpadInitializationDictionary];
159
160  BreakpadRef b = BreakpadCreate(breakpadDictionary);
161  STAssertNotNULL(b, @"BreakpadCreate failed with valid dictionary!");
162
163  BreakpadAddUploadParameter(b,
164                             @"key",
165                             @"value");
166
167  // Test that it did not add the key/value directly, e.g. without
168  // prepending the key with the prefix.
169  STAssertNil(BreakpadKeyValue(b, @"key"),
170              @"AddUploadParameter added key directly to dictionary"
171              " instead of prepending it!");
172
173  NSString *prependedKeyname =
174    [@BREAKPAD_SERVER_PARAMETER_PREFIX stringByAppendingString:@"key"];
175
176  STAssertEqualStrings(BreakpadKeyValue(b, prependedKeyname),
177                       @"value",
178                       @"Calling BreakpadAddUploadParameter did not prepend "
179                       "key name");
180  BreakpadRelease(b);
181}
182
183// Test that when we do on-demand minidump generation,
184// the exception code/type/thread are set properly.
185- (void)testFilterCallbackReturnsFalse {
186  NSMutableDictionary *breakpadDictionary =
187    [self breakpadInitializationDictionary];
188
189  BreakpadRef b = BreakpadCreate(breakpadDictionary);
190  STAssertNotNULL(b, @"BreakpadCreate failed with valid dictionary!");
191  BreakpadSetFilterCallback(b, &myBreakpadCallback, self);
192
193  // This causes the callback to return false, meaning
194  // Breakpad won't take the exception
195  shouldHandleException_ = false;
196
197  [self initializeExceptionStateVariables];
198  STAssertEquals(last_exception_type_, kNoLastExceptionType,
199                 @"Last exception type not initialized correctly.");
200  STAssertEquals(last_exception_code_, kNoLastExceptionCode,
201                 @"Last exception code not initialized correctly.");
202  STAssertEquals(last_exception_thread_, kNoLastExceptionThread,
203                 @"Last exception thread is not initialized correctly.");
204
205  // Cause Breakpad's exception handler to be invoked.
206  BreakpadGenerateAndSendReport(b);
207
208  STAssertEquals(last_exception_type_, 0,
209                 @"Last exception type is not 0 for on demand");
210  STAssertEquals(last_exception_code_, 0,
211                 @"Last exception code is not 0 for on demand");
212  STAssertEquals(last_exception_thread_, mach_thread_self(),
213                 @"Last exception thread is not mach_thread_self() "
214                 "for on demand");
215}
216
217@end
218