Skip to content

Commit 9fe55b0

Browse files
committed
Add basic tests for MacVim
Add a few tests for MacVim using XCTest. Previously MacVim had essentially zero automatic tests. While we run the Vim tests in CI, those mostly exercise Vim features, not MacVim. These new tests are Objective C tests that are designed for MacVim-layer functionality instead. Only adding a few tests (both unit tests and integration tests) now to get started and set up a template for different types of tests but more will be added later. Since MacVim consists of a lot of macOS API calls, a lot of those functionality are not easy to test automatically as they rely on behaviors out of MacVim's control (e.g. full screen functionality). The goal here is not to get 100% test coverage, but to catch areas prone to have regressions, code with non-straightforward logic, functionality that are annoying to manually test (e.g. key input), code blocks that perform calculations and can be easily unit tested. Areas that would be harder to add tests for include integration with macOS, e.g. full screen or dual monitor, interacting with the windowing system, or things that require screenshots in order to properly validate. The goal here is to progressively add more tests as we touch different parts of MacVim to help catch regressions. Old code that works and isn't being touched will be lower priority as it's unlikely they will suddenly break.
1 parent 1f30462 commit 9fe55b0

12 files changed

Lines changed: 1002 additions & 98 deletions

File tree

.github/workflows/ci-macvim.yaml

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,11 @@ jobs:
8787
# Later, we pass the --enable-sparkle_1 flag to configure to set the corresponding ifdef.
8888
ln -fhs Sparkle_1.framework src/MacVim/Sparkle.framework
8989
90+
# Sparkle shows a dialog asking if the user wants to check for updates on 2nd launch of
91+
# MacVim. On Sparkle 1 this is annoyingly a modal dialog box and interferes with tests.
92+
# Just disable it by pre-setting to not check for updates.
93+
defaults write org.vim.MacVim SUEnableAutomaticChecks 0
94+
9095
- name: Set up Xcode
9196
if: matrix.xcode != ''
9297
run: |
@@ -310,7 +315,12 @@ jobs:
310315
echo 'MacVim_xcode8.xcodeproj is outdated. Run "make -C src macvim-xcodeproj-compat" to re-generate it.'; false
311316
fi
312317
313-
- name: Build test binaries
318+
- name: Test MacVim
319+
timeout-minutes: 10
320+
run: |
321+
make ${MAKE_BUILD_ARGS} -C src macvim-tests
322+
323+
- name: Build Vim test binaries
314324
run: |
315325
# Build the unit test binaries first. With link-time-optimization they take some time to link. Running them
316326
# separately de-couples them from the timeout in tests, and allow us to build in parallel jobs (since tests
@@ -320,11 +330,11 @@ jobs:
320330
set -o verbose
321331
make ${MAKE_BUILD_ARGS} -j${NPROC} -C src unittesttargets
322332
323-
- name: Test
333+
- name: Test Vim
324334
timeout-minutes: 20
325335
run: make ${MAKE_BUILD_ARGS} test
326336

327-
- name: Test GUI
337+
- name: Test Vim (GUI)
328338
timeout-minutes: 20
329339
run: |
330340
make ${MAKE_BUILD_ARGS} -C src/testdir clean

src/MacVim/MMAppController.m

Lines changed: 72 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,7 @@ - (NSArray *)filterOpenFiles:(NSArray *)filenames
121121
- (void)handleXcodeModEvent:(NSAppleEventDescriptor *)event
122122
replyEvent:(NSAppleEventDescriptor *)reply;
123123
#endif
124+
+ (NSDictionary*)parseOpenURL:(NSURL*)url;
124125
- (void)handleGetURLEvent:(NSAppleEventDescriptor *)event
125126
replyEvent:(NSAppleEventDescriptor *)reply;
126127
- (NSMutableDictionary *)extractArgumentsFromOdocEvent:
@@ -523,30 +524,7 @@ - (void)applicationDidFinishLaunching:(NSNotification *)notification
523524
// If the current version is larger, set that to be stored. Don't
524525
// want to do it otherwise to prevent testing older versions flipping
525526
// the stored version back to an old one.
526-
NSArray<NSString*> *lastUsedVersionItems = [lastUsedVersion componentsSeparatedByString:@"."];
527-
NSArray<NSString*> *currentVersionItems = [currentVersion componentsSeparatedByString:@"."];
528-
// Compare two arrays lexographically. We just assume that version
529-
// numbers are also X.Y.Z… with no "beta" etc texts.
530-
BOOL currentVersionLarger = NO;
531-
for (int i = 0; i < currentVersionItems.count || i < lastUsedVersionItems.count; i++) {
532-
if (i >= currentVersionItems.count) {
533-
currentVersionLarger = NO;
534-
break;
535-
}
536-
if (i >= lastUsedVersionItems.count) {
537-
currentVersionLarger = YES;
538-
break;
539-
}
540-
if (currentVersionItems[i].integerValue > lastUsedVersionItems[i].integerValue) {
541-
currentVersionLarger = YES;
542-
break;
543-
}
544-
else if (currentVersionItems[i].integerValue < lastUsedVersionItems[i].integerValue) {
545-
currentVersionLarger = NO;
546-
break;
547-
}
548-
}
549-
527+
const BOOL currentVersionLarger = (compareSemanticVersions(lastUsedVersion, currentVersion) == 1);
550528
if (currentVersionLarger) {
551529
[ud setValue:currentVersion forKey:MMLastUsedBundleVersionKey];
552530

@@ -2130,6 +2108,73 @@ - (void)handleXcodeModEvent:(NSAppleEventDescriptor *)event
21302108
}
21312109
#endif
21322110

2111+
+ (NSDictionary*)parseOpenURL:(NSURL*)url
2112+
{
2113+
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
2114+
2115+
// Parse query ("url=file://...&line=14") into a dictionary
2116+
NSArray *queries = [[url query] componentsSeparatedByString:@"&"];
2117+
NSEnumerator *enumerator = [queries objectEnumerator];
2118+
NSString *param;
2119+
while ((param = [enumerator nextObject])) {
2120+
// query: <field>=<value>
2121+
NSArray *arr = [param componentsSeparatedByString:@"="];
2122+
if ([arr count] == 2) {
2123+
// parse field
2124+
NSString *f = [arr objectAtIndex:0];
2125+
#if MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_11
2126+
f = [f stringByRemovingPercentEncoding];
2127+
#else
2128+
f = [f stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
2129+
#endif
2130+
2131+
// parse value
2132+
NSString *v = [arr objectAtIndex:1];
2133+
2134+
// We need to decode the parameters here because most URL
2135+
// parsers treat the query component as needing to be decoded
2136+
// instead of treating it as is. It does mean that a link to
2137+
// open file "/tmp/file name.txt" will be
2138+
// mvim://open?url=file:///tmp/file%2520name.txt to encode a
2139+
// URL of file:///tmp/file%20name.txt. This double-encoding is
2140+
// intentional to follow the URL spec.
2141+
#if MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_11
2142+
v = [v stringByRemovingPercentEncoding];
2143+
#else
2144+
v = [v stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
2145+
#endif
2146+
2147+
if ([f isEqualToString:@"url"]) {
2148+
// Since the URL scheme uses a double-encoding due to a
2149+
// file:// URL encoded in another mvim: one, existing tools
2150+
// like iTerm2 could sometimes erroneously only do a single
2151+
// encode. To maximize compatiblity, we re-encode invalid
2152+
// characters if we detect them as they would not work
2153+
// later on when we pass this string to URLWithString.
2154+
//
2155+
// E.g. mvim://open?uri=file:///foo%20bar => "file:///foo bar"
2156+
// which is not a valid URL, so we re-encode it to
2157+
// file:///foo%20bar here. The only important case is to
2158+
// not touch the "%" character as it introduces ambiguity
2159+
// and the re-encoding is a nice compatibility feature, but
2160+
// the canonical form should be double-encoded, i.e.
2161+
// mvim://open?uri=file:///foo%2520bar
2162+
#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_10
2163+
if (AVAILABLE_MAC_OS(10, 10)) {
2164+
NSMutableCharacterSet *charSet = [NSMutableCharacterSet characterSetWithCharactersInString:@"%"];
2165+
[charSet formUnionWithCharacterSet:NSCharacterSet.URLHostAllowedCharacterSet];
2166+
[charSet formUnionWithCharacterSet:NSCharacterSet.URLPathAllowedCharacterSet];
2167+
v = [v stringByAddingPercentEncodingWithAllowedCharacters:charSet];
2168+
}
2169+
#endif
2170+
}
2171+
2172+
[dict setValue:v forKey:f];
2173+
}
2174+
}
2175+
return dict;
2176+
}
2177+
21332178
- (void)handleGetURLEvent:(NSAppleEventDescriptor *)event
21342179
replyEvent:(NSAppleEventDescriptor *)reply
21352180
{
@@ -2138,7 +2183,7 @@ - (void)handleGetURLEvent:(NSAppleEventDescriptor *)event
21382183
stringValue]];
21392184

21402185
// We try to be compatible with TextMate's URL scheme here, as documented
2141-
// at http://blog.macromates.com/2007/the-textmate-url-scheme/ . Currently,
2186+
// at https://macromates.com/blog/2007/the-textmate-url-scheme/ . Currently,
21422187
// this means that:
21432188
//
21442189
// The format is: mvim://open?<arguments> where arguments can be:
@@ -2151,66 +2196,8 @@ - (void)handleGetURLEvent:(NSAppleEventDescriptor *)event
21512196
// Example: mvim://open?url=file:///etc/profile&line=20
21522197

21532198
if ([[url host] isEqualToString:@"open"]) {
2154-
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
2155-
2156-
// Parse query ("url=file://...&line=14") into a dictionary
2157-
NSArray *queries = [[url query] componentsSeparatedByString:@"&"];
2158-
NSEnumerator *enumerator = [queries objectEnumerator];
2159-
NSString *param;
2160-
while ((param = [enumerator nextObject])) {
2161-
// query: <field>=<value>
2162-
NSArray *arr = [param componentsSeparatedByString:@"="];
2163-
if ([arr count] == 2) {
2164-
// parse field
2165-
NSString *f = [arr objectAtIndex:0];
2166-
#if MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_11
2167-
f = [f stringByRemovingPercentEncoding];
2168-
#else
2169-
f = [f stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
2170-
#endif
2171-
2172-
// parse value
2173-
NSString *v = [arr objectAtIndex:1];
2174-
2175-
// We need to decode the parameters here because most URL
2176-
// parsers treat the query component as needing to be decoded
2177-
// instead of treating it as is. It does mean that a link to
2178-
// open file "/tmp/file name.txt" will be
2179-
// mvim://open?url=file:///tmp/file%2520name.txt to encode a
2180-
// URL of file:///tmp/file%20name.txt. This double-encoding is
2181-
// intentional to follow the URL spec.
2182-
#if MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_11
2183-
v = [v stringByRemovingPercentEncoding];
2184-
#else
2185-
v = [v stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
2186-
#endif
2187-
2188-
if ([f isEqualToString:@"url"]) {
2189-
// Since the URL scheme uses a double-encoding due to a
2190-
// file:// URL encoded in another mvim: one, existing tools
2191-
// like iTerm2 could sometimes erroneously only do a single
2192-
// encode. To maximize compatiblity, we re-encode invalid
2193-
// characters if we detect them as they would not work
2194-
// later on when we pass this string to URLWithString.
2195-
//
2196-
// E.g. mvim://open?uri=file:///foo%20bar => "file:///foo bar"
2197-
// which is not a valid URL, so we re-encode it to
2198-
// file:///foo%20bar here. The only important case is to
2199-
// not touch the "%" character as it introduces ambiguity
2200-
// and the re-encoding is a nice compatibility feature, but
2201-
// the canonical form should be double-encoded, i.e.
2202-
// mvim://open?uri=file:///foo%2520bar
2203-
#if MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_11
2204-
NSMutableCharacterSet *charSet = [NSMutableCharacterSet characterSetWithCharactersInString:@"%"];
2205-
[charSet formUnionWithCharacterSet:NSCharacterSet.URLHostAllowedCharacterSet];
2206-
[charSet formUnionWithCharacterSet:NSCharacterSet.URLPathAllowedCharacterSet];
2207-
v = [v stringByAddingPercentEncodingWithAllowedCharacters:charSet];
2208-
#endif
2209-
}
2210-
2211-
[dict setValue:v forKey:f];
2212-
}
2213-
}
2199+
// Parse the URL and process it
2200+
NSDictionary *dict = [MMAppController parseOpenURL:url];
22142201

22152202
// Actually open the file.
22162203
NSString *file = [dict objectForKey:@"url"];

src/MacVim/MMBackend.m

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2319,6 +2319,10 @@ - (void)handleInputEvent:(int)msgid data:(NSData *)data
23192319
[self setImState:NO];
23202320
} else if (BackingPropertiesChangedMsgID == msgid) {
23212321
[self redrawScreen];
2322+
} else if (LoopBackMsgID == msgid) {
2323+
// This is a debug message used for confirming a message has been
2324+
// received and echoed back to caller for synchronization purpose.
2325+
[self queueMessage:msgid data:nil];
23222326
} else {
23232327
ASLogWarn(@"Unknown message received (msgid=%d)", msgid);
23242328
}

src/MacVim/MacVim.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -344,6 +344,7 @@ extern const char * const MMVimMsgIDStrings[];
344344
MSG(EnableThinStrokesMsgID) \
345345
MSG(DisableThinStrokesMsgID) \
346346
MSG(ShowDefinitionMsgID) \
347+
MSG(LoopBackMsgID) /* Simple message that Vim will reflect back to MacVim */ \
347348
MSG(LastMsgID) \
348349

349350
enum {

0 commit comments

Comments
 (0)