Add very minimal support for a scripting dictionary — only the getURL

AppleEvent — and add an XCTestCase that can run and verify results of
AppleScripts that target Evergreen.
This commit is contained in:
Olof Hellman 2018-01-07 15:13:10 -08:00
parent 0a1642abdf
commit b04a4b83f2
8 changed files with 283 additions and 0 deletions

View File

@ -0,0 +1,33 @@
//
// AppleEventUtils.swift
// EvergreenTests
//
// Created by Olof Hellman on 1/7/18.
// Copyright © 2018 Olof Hellman. All rights reserved.
//
import Foundation
/*
@function FourCharCode()
@brief FourCharCode values like OSType, DescType or AEKeyword are really just
4 byte values commonly represented as values like 'odoc' where each byte is
represented as its ASCII character. This function turns a swift string into
its FourCharCode equivalent, as swift doesn't recognize FourCharCode types
natively just yet. With this extension, one can use
"odoc".FourCharCode()
where one would really want to use 'odoc'
*/
extension String {
func FourCharCode() -> FourCharCode {
var sum: UInt32 = 0
guard ( self.count == 4) else {
print ("error: FourCharCode() expected a 4 character string")
return 0
}
for scalar in self.unicodeScalars {
sum = (sum * 256) + scalar.value
}
return (sum)
}
}

View File

@ -0,0 +1,54 @@
//
// NSAppleEventDescriptor+UserRecordFields.swift
// EvergreenTests
//
// Created by Olof Hellman on 1/7/18.
// Copyright © 2018 Olof Hellman. All rights reserved.
//
import Foundation
/*
@function usrfDictionary()
@brief When an apple event record contains key-value pairs for which the keys are
not associated with FourCharCode keys, the keys and values appear in a
"user record fields" AEList, in which the odd items are the keys and the
even items are the values. This function unpacks user record fields and
returns an analogous Swift dictionary
*/
extension NSAppleEventDescriptor {
public func usrfDictionary() -> [String:NSAppleEventDescriptor] {
guard self.isRecordDescriptor else {
print ("error: usrfDictionary() expected input to be a record")
return [:]
}
guard let usrfList = self.forKeyword("usrf".FourCharCode()) else {
print ("error: usrfDictionary() couldn't find usrf")
return [:]
}
let listCount = usrfList.numberOfItems
guard (listCount%2 == 0) else {
print ("error: usrfDictionary() expected even number of items in usrf")
return [:]
}
var usrfDictionary:[String:NSAppleEventDescriptor] = [:]
var processedItems = 0
while (processedItems < listCount) {
processedItems = processedItems + 2
guard let nthlabel = usrfList.atIndex(processedItems-1) else {
print("usrfDictionary() couldn't get item \(processedItems+1) in usrf list")
continue
}
guard let nthvalue = usrfList.atIndex(processedItems) else {
print("usrfDictionary() couldn't get item \(processedItems+2) in usrf list")
continue
}
guard let nthLabelString = nthlabel.stringValue else {
print("usrfDictionary() expected label to be a String")
continue
}
usrfDictionary[nthLabelString] = nthvalue
}
return usrfDictionary;
}
}

View File

@ -124,6 +124,14 @@
84FB9A2F1EDCD6C4003D53B9 /* Sparkle.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 84FB9A2D1EDCD6B8003D53B9 /* Sparkle.framework */; };
84FB9A301EDCD6C4003D53B9 /* Sparkle.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 84FB9A2D1EDCD6B8003D53B9 /* Sparkle.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
84FF69B11FC3793300DC198E /* FaviconURLFinder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84FF69B01FC3793300DC198E /* FaviconURLFinder.swift */; };
D5558FD32002245C0066386B /* ScriptingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5558FD22002245C0066386B /* ScriptingTests.swift */; };
D5558FD5200225680066386B /* NSAppleEventDescriptor+UserRecordFields.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5558FD4200225680066386B /* NSAppleEventDescriptor+UserRecordFields.swift */; };
D5558FD9200228D30066386B /* AppleEventUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5558FD7200228B80066386B /* AppleEventUtils.swift */; };
D5907CA0200232A1005947E5 /* testGenericScript.applescript in Sources */ = {isa = PBXBuildFile; fileRef = D5907C9D20023249005947E5 /* testGenericScript.applescript */; };
D5907CA1200232A1005947E5 /* testGetURL.applescript in Sources */ = {isa = PBXBuildFile; fileRef = D5558FD1200223F60066386B /* testGetURL.applescript */; };
D5907CA2200232AD005947E5 /* testGenericScript.applescript in CopyFiles */ = {isa = PBXBuildFile; fileRef = D5907C9D20023249005947E5 /* testGenericScript.applescript */; };
D5907CA3200232AF005947E5 /* testGetURL.applescript in CopyFiles */ = {isa = PBXBuildFile; fileRef = D5558FD1200223F60066386B /* testGetURL.applescript */; };
D5D1751220020B980047B29D /* Evergreen.sdef in Resources */ = {isa = PBXBuildFile; fileRef = D5D175012002039D0047B29D /* Evergreen.sdef */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@ -411,6 +419,17 @@
name = "Embed Frameworks";
runOnlyForDeploymentPostprocessing = 0;
};
D5907C9B20022EC7005947E5 /* CopyFiles */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = TestScripts;
dstSubfolderSpec = 7;
files = (
D5907CA2200232AD005947E5 /* testGenericScript.applescript in CopyFiles */,
D5907CA3200232AF005947E5 /* testGetURL.applescript in CopyFiles */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
@ -527,6 +546,12 @@
84F2D5391FC2308B00998D64 /* UnreadFeed.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnreadFeed.swift; sourceTree = "<group>"; };
84FB9A2D1EDCD6B8003D53B9 /* Sparkle.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Sparkle.framework; path = Frameworks/Vendor/Sparkle.framework; sourceTree = SOURCE_ROOT; };
84FF69B01FC3793300DC198E /* FaviconURLFinder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FaviconURLFinder.swift; sourceTree = "<group>"; };
D5558FD1200223F60066386B /* testGetURL.applescript */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.applescript; path = testGetURL.applescript; sourceTree = "<group>"; };
D5558FD22002245C0066386B /* ScriptingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ScriptingTests.swift; path = EvergreenTests/ScriptingTests/ScriptingTests.swift; sourceTree = SOURCE_ROOT; };
D5558FD4200225680066386B /* NSAppleEventDescriptor+UserRecordFields.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = "NSAppleEventDescriptor+UserRecordFields.swift"; path = "AppleEvents/NSAppleEventDescriptor+UserRecordFields.swift"; sourceTree = SOURCE_ROOT; };
D5558FD7200228B80066386B /* AppleEventUtils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppleEventUtils.swift; sourceTree = "<group>"; };
D5907C9D20023249005947E5 /* testGenericScript.applescript */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.applescript; path = testGenericScript.applescript; sourceTree = "<group>"; };
D5D175012002039D0047B29D /* Evergreen.sdef */ = {isa = PBXFileReference; lastKnownFileType = text.xml; name = Evergreen.sdef; path = ../Resources/Evergreen.sdef; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@ -828,6 +853,7 @@
849A97991ED9EFB6007D329B /* Resources */ = {
isa = PBXGroup;
children = (
D5D175012002039D0047B29D /* Evergreen.sdef */,
849C646C1ED37A5D003D8FC0 /* Info.plist */,
84EB380F1FBA8B9F000D2111 /* KeyboardShortcuts */,
);
@ -872,6 +898,7 @@
848F6AE31FC29CFA002D422E /* Favicons */,
845213211FCA5B10003B6E93 /* Images */,
849A97961ED9EFAA007D329B /* Extensions */,
D5558FD6200227E60066386B /* AppleEvents */,
849A97991ED9EFB6007D329B /* Resources */,
84FB9A2C1EDCD6A4003D53B9 /* Frameworks */,
849C64741ED37A5D003D8FC0 /* EvergreenTests */,
@ -902,6 +929,7 @@
849C64741ED37A5D003D8FC0 /* EvergreenTests */ = {
isa = PBXGroup;
children = (
D5558FC020021C290066386B /* ScriptingTests */,
849C64751ED37A5D003D8FC0 /* EvergreenTests.swift */,
849C64771ED37A5D003D8FC0 /* Info.plist */,
);
@ -1035,6 +1063,33 @@
path = Evergreen/Extensions;
sourceTree = "<group>";
};
D5558FC020021C290066386B /* ScriptingTests */ = {
isa = PBXGroup;
children = (
D5558FCF20021C590066386B /* scripts */,
D5558FD22002245C0066386B /* ScriptingTests.swift */,
);
path = ScriptingTests;
sourceTree = "<group>";
};
D5558FCF20021C590066386B /* scripts */ = {
isa = PBXGroup;
children = (
D5907C9D20023249005947E5 /* testGenericScript.applescript */,
D5558FD1200223F60066386B /* testGetURL.applescript */,
);
path = scripts;
sourceTree = "<group>";
};
D5558FD6200227E60066386B /* AppleEvents */ = {
isa = PBXGroup;
children = (
D5558FD7200228B80066386B /* AppleEventUtils.swift */,
D5558FD4200225680066386B /* NSAppleEventDescriptor+UserRecordFields.swift */,
);
path = AppleEvents;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
@ -1075,6 +1130,7 @@
849C646D1ED37A5D003D8FC0 /* Sources */,
849C646E1ED37A5D003D8FC0 /* Frameworks */,
849C646F1ED37A5D003D8FC0 /* Resources */,
D5907C9B20022EC7005947E5 /* CopyFiles */,
);
buildRules = (
);
@ -1374,6 +1430,7 @@
849A97B21ED9FA69007D329B /* MainWindow.storyboard in Resources */,
849A979C1ED9EFEB007D329B /* styleSheet.css in Resources */,
849A97A61ED9F94D007D329B /* Preferences.storyboard in Resources */,
D5D1751220020B980047B29D /* Evergreen.sdef in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -1489,7 +1546,12 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
D5558FD5200225680066386B /* NSAppleEventDescriptor+UserRecordFields.swift in Sources */,
D5558FD9200228D30066386B /* AppleEventUtils.swift in Sources */,
D5907CA1200232A1005947E5 /* testGetURL.applescript in Sources */,
849C64761ED37A5D003D8FC0 /* EvergreenTests.swift in Sources */,
D5558FD32002245C0066386B /* ScriptingTests.swift in Sources */,
D5907CA0200232A1005947E5 /* testGenericScript.applescript in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};

View File

@ -37,5 +37,9 @@
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
<key>NSAppleScriptEnabled</key>
<true/>
<key>OSAScriptingDefinition</key>
<string>Evergreen.sdef</string>
</dict>
</plist>

View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE dictionary SYSTEM "file://localhost/System/Library/DTDs/sdef.dtd">
<!-- declare the namespace for using XInclude so we can include the standard suite -->
<dictionary title="sa;lfsdf" xmlns:xi="http://www.w3.org/2003/XInclude">
<suite name="Internet Suite" code="GURL"
description="Standard Internet Suite.">
<command name="open location" code="GURLGURL" description="opens the given url.">
<direct-parameter type="text"/>
</command>
</suite>
</dictionary>

View File

@ -0,0 +1,81 @@
//
// ScriptingTests.swift
// EvergreenTests
//
// Created by Olof Hellman on 1/7/18.
// Copyright © 2018 Olof Hellman. All rights reserved.
//
import XCTest
class ScriptingTests: XCTestCase {
override func setUp() {
super.setUp()
}
override func tearDown() {
super.tearDown()
}
/*
@function doIndividualScript
@param filename -- name of a .applescript (sans extention) in the test bundle's
Resources/TestScripts directory
@brief given a file, loads the script and runs it. Expects a result from running
the script of the form
{test_result:true, script_result:<anything>}
if the test_result is false or is missing, the test fails
@return the value of script_result, if any
*/
func doIndividualScript(filename:String) -> NSAppleEventDescriptor? {
var errorDict: NSDictionary? = nil
let testBundle = Bundle(for: type(of: self))
let url = testBundle.url(forResource:filename, withExtension:"applescript", subdirectory:"TestScripts")
guard let testScriptUrl = url else {
XCTFail("Failed Getting script URL")
return nil
}
guard let testScript = NSAppleScript(contentsOf: testScriptUrl, error: &errorDict) else {
XCTFail("Failed initializing NSAppleScript")
print ("error is \(String(describing: errorDict))")
return nil
}
let scriptResult = testScript.executeAndReturnError(&errorDict)
if (errorDict != nil) {
XCTFail("Failed executing script")
print ("error is \(String(describing: errorDict))")
return nil
}
let usrfDictionary = scriptResult.usrfDictionary()
guard let testResult = usrfDictionary["test_result"] else {
XCTFail("test script didn't return test result in usrf")
return nil
}
XCTAssert(testResult.booleanValue == true, "test_result should be true")
return usrfDictionary["script_result"]
}
/*
@function testGenericScript
@brief An example of how a script can be run as part of an XCTest
the applescript returns
{test_result:true, script_result:"Geoducks!"}
doIndividualScript() verifies the test_result portion
this code verifies the script_result portion
*/
func testGenericScript() {
let scriptResult = doIndividualScript(filename: "testGenericScript")
XCTAssert( scriptResult?.stringValue == "Geoducks!")
}
func testGetUrlScript() {
_ = doIndividualScript(filename: "testGetURL")
}
}

View File

@ -0,0 +1,13 @@
-- Evergreen scripting unit tests expect a dictionary to be returned from a script
-- containing either
-- {test_result:true}
-- to indicate success or
-- {test_result:false}
-- to indicate failure
-- Data can be passed back to unit test code by including a script_result field
-- for example this script returns "Geoducks!" as the result
-- this can be used as part of XCTest verification
-- see the testGenericScript() function in the ScriptingTests XCTestCase
return {test_result:true, script_result:"Geoducks!"}

View File

@ -0,0 +1,19 @@
try
tell application "Evergreen"
open location "http://scripting.com/rss"
end tell
on error message
return {test_result:false, script_result:message}
end
-- open location is not expected to return a value
-- trying to access result should trigger an error, and that indicates a successful test
try
set getURLResult to the result
set testResult to false
on error message
set testResult to true
end try
return {test_result:testResult}