Skip to content

Commit 3368c98

Browse files
committed
feat: Add maxBuffer configuration for command output in iOS and macOS test scripts
1 parent 9388c48 commit 3368c98

4 files changed

Lines changed: 172 additions & 57 deletions

File tree

NativeScript/ffi/Tasks.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
#include "Tasks.h"
2+
#include <utility>
23

34
namespace nativescript {
45

56
void Tasks::Register(std::function<void()> task) {
6-
tasks_.push_back(task);
7+
tasks_.emplace_back(std::move(task));
78
}
89

910
void Tasks::Drain() {

TestRunner/app/tests/ApiTests.js

Lines changed: 133 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -135,29 +135,49 @@ describe(module.id, function () {
135135
});
136136

137137
it("instanceOfUITabBarController", function () {
138-
if (pendingIfMissingSymbol("UITabBarController") ||
139-
pendingIfMissingSymbol("UIViewController") ||
140-
pendingIfMissingSymbol("UIResponder")) {
138+
if (hasGlobalSymbol("UITabBarController") &&
139+
hasGlobalSymbol("UIViewController") &&
140+
hasGlobalSymbol("UIResponder")) {
141+
var uiObject = UITabBarController.alloc().init();
142+
expect(uiObject instanceof UITabBarController).toBe(true);
143+
expect(uiObject instanceof UIViewController).toBe(true);
144+
expect(uiObject instanceof UIResponder).toBe(true);
145+
expect(uiObject instanceof NSObject).toBe(true);
141146
return;
142147
}
143148

144-
var object = UITabBarController.alloc().init();
145-
expect(object instanceof UITabBarController).toBe(true);
146-
expect(object instanceof UIViewController).toBe(true);
147-
expect(object instanceof UIResponder).toBe(true);
148-
expect(object instanceof NSObject).toBe(true);
149+
if (hasGlobalSymbol("NSTabViewController") &&
150+
hasGlobalSymbol("NSViewController") &&
151+
hasGlobalSymbol("NSResponder")) {
152+
var nsObject = NSTabViewController.alloc().init();
153+
expect(nsObject instanceof NSTabViewController).toBe(true);
154+
expect(nsObject instanceof NSViewController).toBe(true);
155+
expect(nsObject instanceof NSResponder).toBe(true);
156+
expect(nsObject instanceof NSObject).toBe(true);
157+
return;
158+
}
159+
160+
pending("Neither UIKit nor AppKit tab/view controller hierarchy is available.");
149161
});
150162

151163
it("Appearance", function () {
152-
if (pendingIfMissingSymbol("UILabel") || pendingIfMissingSymbol("UIColor")) {
164+
if (hasGlobalSymbol("UILabel") && hasGlobalSymbol("UIColor")) {
165+
expect(UILabel.appearance().description.indexOf('<Customizable class: UILabel>')).not.toBe(-1);
166+
167+
UILabel.appearance().textColor = UIColor.redColor;
168+
expect(UILabel.appearance().textColor).toBe(UIColor.redColor);
153169
return;
154170
}
155171

156-
expect(UILabel.appearance().description.indexOf('<Customizable class: UILabel>')).not.toBe(-1);
172+
if (hasGlobalSymbol("NSAppearance") && hasGlobalSymbol("NSAppearanceNameAqua")) {
173+
var appearance = NSAppearance.appearanceNamed(NSAppearanceNameAqua);
174+
expect(appearance).toBeDefined();
175+
expect(appearance).not.toBeNull();
176+
expect(appearance.name).toContain("Aqua");
177+
return;
178+
}
157179

158-
UILabel.appearance().textColor = UIColor.redColor;
159-
expect(UILabel.appearance().textColor).toBe(UIColor.redColor);
160-
// expect(UILabel.appearance().constructor).toBe(UILabel);
180+
pending("Neither UIKit nor AppKit appearance APIs are available.");
161181
});
162182

163183
it("ReadonlyPropertyInProtocolAndOverrideWithSetterInInterface", function () {
@@ -248,42 +268,88 @@ describe(module.id, function () {
248268
});
249269

250270
it("SpecialCaseProperty_When_InstancesRespondToSelector:_IsFalse", function () {
251-
if (pendingIfMissingSymbol("UITextField")) {
271+
var uiTextField = hasGlobalSymbol("UITextField") ? global.UITextField : null;
272+
var nsView = hasGlobalSymbol("NSView") ? global.NSView : null;
273+
if (uiTextField) {
274+
var uiField = new uiTextField();
275+
expect(uiField.secureTextEntry).toBe(false);
276+
uiField.secureTextEntry = true;
277+
expect(uiField.secureTextEntry).toBe(true);
278+
return;
279+
}
280+
281+
if (!nsView) {
282+
pending("No suitable platform class is available for special-case property checks.");
252283
return;
253284
}
254285

255-
var field = new UITextField();
256-
expect(field.secureTextEntry).toBe(false);
257-
field.secureTextEntry = true;
258-
expect(field.secureTextEntry).toBe(true);
286+
var field = nsView.alloc().init();
287+
expect(field.hidden).toBe(false);
288+
field.hidden = true;
289+
expect(field.hidden).toBe(true);
259290
});
260291

261292
it("SpecialCaseProperty_When_CustomSelector_ImplementedInJS", function () {
262-
if (pendingIfMissingSymbol("UITextField")) {
293+
var uiTextField = hasGlobalSymbol("UITextField") ? global.UITextField : null;
294+
var nsView = hasGlobalSymbol("NSView") ? global.NSView : null;
295+
if (uiTextField) {
296+
var uiField = new (uiTextField.extend({
297+
get secureTextEntry() {
298+
TNSLog("getter");
299+
return this._secureTextEntry;
300+
},
301+
set secureTextEntry(val) {
302+
this._secureTextEntry = val;
303+
TNSLog("setter:" + val);
304+
}
305+
}))();
306+
var uiExpectedOutput = "";
307+
308+
expect(uiField.secureTextEntry).toBeUndefined(); uiExpectedOutput+="getter";
309+
310+
uiField.secureTextEntry = true; uiExpectedOutput+="setter:true";
311+
312+
expect(uiField.secureTextEntry).toBe(true); uiExpectedOutput+="getter";
313+
314+
uiField.secureTextEntry = false; uiExpectedOutput+="setter:false";
315+
316+
expect(uiField.secureTextEntry).toBe(false); uiExpectedOutput+="getter";
317+
318+
expect(TNSGetOutput()).toBe(uiExpectedOutput);
319+
return;
320+
}
321+
322+
if (!nsView) {
323+
pending("No suitable platform class is available for special-case property checks.");
263324
return;
264325
}
265326

266-
var field = new (UITextField.extend({
267-
get secureTextEntry() {
327+
var overrides = {};
328+
Object.defineProperty(overrides, "hidden", {
329+
get: function () {
268330
TNSLog("getter");
269-
return this._secureTextEntry;
331+
return this._value;
270332
},
271-
set secureTextEntry(val) {
272-
this._secureTextEntry = val;
333+
set: function (val) {
334+
this._value = val;
273335
TNSLog("setter:" + val);
274-
}
275-
}))();
336+
},
337+
enumerable: true,
338+
configurable: true
339+
});
340+
341+
var field = new (nsView.extend(overrides))();
276342
var expectedOutput = "";
277343

278-
expect(field.secureTextEntry).toBeUndefined(); expectedOutput+="getter";
344+
expect(field.hidden).toBeUndefined(); expectedOutput+="getter";
279345

280-
field.secureTextEntry = true; expectedOutput+="setter:true";
346+
field.hidden = true; expectedOutput+="setter:true";
281347

282-
expect(field.secureTextEntry).toBe(true); expectedOutput+="getter";
348+
expect(field.hidden).toBe(true); expectedOutput+="getter";
283349

284-
field.secureTextEntry = false; expectedOutput+="setter:false";
350+
field.hidden = false; expectedOutput+="setter:false";
285351

286-
expect(field.secureTextEntry).toBe(false); expectedOutput+="getter";
352+
expect(field.hidden).toBe(false); expectedOutput+="getter";
287353

288354
expect(TNSGetOutput()).toBe(expectedOutput);
289355
});
@@ -802,25 +868,32 @@ describe(module.id, function () {
802868
// });
803869

804870
it("Unimplemented properties from UIBarItem class should be provided by the inheritors", function () {
805-
var classConstructors = ["UIBarButtonItem", "UITabBarItem"];
806-
for (var className of classConstructors) {
807-
if (!hasGlobalSymbol(className)) {
808-
pending(className + " is not available in this runtime/platform configuration.");
809-
return;
871+
if (hasGlobalSymbol("UIBarButtonItem") && hasGlobalSymbol("UITabBarItem")) {
872+
var iosClassConstructors = ["UIBarButtonItem", "UITabBarItem"];
873+
var iosProps = ["enabled", "image", "imageInsets", "title"];
874+
if (NSProcessInfo.processInfo.isOperatingSystemAtLeastVersion({majorVersion: 11, minorVersion: 0, patchVersion: 0})) {
875+
iosProps = iosProps.concat("landscapeImagePhone", "landscapeImagePhoneInsets");
810876
}
811-
}
812877

813-
var props = ["enabled", "image", "imageInsets", "title"];
814-
if (NSProcessInfo.processInfo.isOperatingSystemAtLeastVersion({majorVersion: 11, minorVersion: 0, patchVersion: 0})) {
815-
props = props.concat("landscapeImagePhone", "landscapeImagePhoneInsets");
878+
for (var iosKlass of iosClassConstructors) {
879+
var iosInstance = new global[iosKlass]();
880+
for (var iosProp of iosProps) {
881+
expect(iosInstance[iosProp]).toBeDefined(`"${iosProp}" must be defined in instances of "${iosKlass}"`);
882+
}
883+
}
884+
return;
816885
}
817886

818-
for (var klass of classConstructors) {
819-
var instance = new global[klass]();
820-
for (var prop of props) {
821-
expect(instance[prop]).toBeDefined(`"${prop}" must be defined in instances of "${klass}"`);
887+
if (hasGlobalSymbol("NSToolbarItem")) {
888+
var toolbarItem = NSToolbarItem.alloc().initWithItemIdentifier("nativescript.test.item");
889+
var macProps = ["enabled", "image", "label", "paletteLabel", "toolTip"];
890+
for (var macProp of macProps) {
891+
expect(toolbarItem[macProp]).toBeDefined(`"${macProp}" must be defined in instances of "NSToolbarItem"`);
822892
}
893+
return;
823894
}
895+
896+
pending("Neither UIKit UIBarItem inheritors nor AppKit NSToolbarItem are available.");
824897
});
825898

826899
it("Unimplemented properties from MTLRenderPassAttachmentDescriptor class should be provided by the inheritors", function () {
@@ -846,16 +919,25 @@ describe(module.id, function () {
846919
});
847920

848921
it("Dynamically load modules", () => {
849-
if (pendingIfMissingSymbol("CMMotionActivityManager")) {
922+
var className = null;
923+
for (var candidate of ["CMMotionActivityManager", "CMMotionManager", "CLLocationManager", "AVAudioEngine"]) {
924+
if (hasGlobalSymbol(candidate)) {
925+
className = candidate;
926+
break;
927+
}
928+
}
929+
930+
if (!className) {
931+
pending("No candidate class from dynamically loaded frameworks is available in this runtime.");
850932
return;
851933
}
852934

853-
// The CMMotionActivityManager interface is defined inside the CoreMotion system framework which is not
854-
// statically loaded and the runtime must dynamically resolve it at runtime.
855-
let activityManager = CMMotionActivityManager.new();
856-
expect(activityManager).not.toBeUndefined();
857-
expect(activityManager).not.toBeNull();
858-
expect(activityManager instanceof CMMotionActivityManager).toBe(true);
935+
// The selected class is expected to come from a framework resolved at runtime.
936+
let dynamicType = global[className];
937+
let instance = dynamicType.new ? dynamicType.new() : dynamicType.alloc().init();
938+
expect(instance).not.toBeUndefined();
939+
expect(instance).not.toBeNull();
940+
expect(instance instanceof dynamicType).toBe(true);
859941
});
860942

861943
it("Optional method returning a structure should use objc_msgSend_stret on x86_64", () => {

scripts/run-tests-ios.js

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,13 @@
1212
// - IOS_SWIFT_VERSION overrides default Swift version (default: 5.0).
1313
// - IOS_COMMAND_TIMEOUT_MS overrides timeout for build/install/simctl commands (default: 3 minutes).
1414
// - IOS_BUILD_TIMEOUT_MS overrides timeout for xcodebuild app build (default: IOS_COMMAND_TIMEOUT_MS).
15+
// - IOS_COMMAND_MAX_BUFFER_BYTES overrides spawnSync maxBuffer for captured command output (default: 64 MiB).
1516
// - IOS_TEST_TIMEOUT_MS overrides max test runtime (default: 2 minutes).
1617
// - IOS_LOG_JUNIT=0 disables streaming TKUnit/JUnit lines to console.
1718
// - IOS_TESTS filters test modules (comma-separated substrings passed to app as -tests).
1819
// - IOS_TEST_INACTIVITY_TIMEOUT_MS overrides max no-log interval (default: 45 seconds).
1920
// - IOS_TEST_LOG_STREAM=0 disables parallel simulator log stream (enabled by default).
21+
// - IOS_SIM_LOG_LOOKBACK sets log-show window used for post-failure diagnostics (default: 45s).
2022

2123
const fs = require("fs");
2224
const path = require("path");
@@ -75,13 +77,24 @@ function parseTimeoutMs(name, fallback) {
7577
return value;
7678
}
7779

80+
function parsePositiveInt(name, fallback) {
81+
const value = Number(process.env[name] || fallback);
82+
if (!Number.isFinite(value) || value <= 0) {
83+
return fallback;
84+
}
85+
86+
return Math.floor(value);
87+
}
88+
7889
const commandTimeoutMs = parseTimeoutMs("IOS_COMMAND_TIMEOUT_MS", 3 * 60 * 1000);
7990
const buildTimeoutMs = parseTimeoutMs("IOS_BUILD_TIMEOUT_MS", commandTimeoutMs);
91+
const commandMaxBufferBytes = parsePositiveInt("IOS_COMMAND_MAX_BUFFER_BYTES", 64 * 1024 * 1024);
8092
const testTimeoutMs = Number(process.env.IOS_TEST_TIMEOUT_MS || 2 * 60 * 1000);
8193
const inactivityTimeoutMs = Number(process.env.IOS_TEST_INACTIVITY_TIMEOUT_MS || 45 * 1000);
8294
const emitJunitLogs = process.env.IOS_LOG_JUNIT !== "0";
8395
const requestedTests = (process.env.IOS_TESTS || "").trim();
8496
const enableLiveLogStream = process.env.IOS_TEST_LOG_STREAM !== "0";
97+
const simulatorLogLookback = process.env.IOS_SIM_LOG_LOOKBACK || "45s";
8598
const consoleLogMarker = "CONSOLE LOG:";
8699

87100
function looksLikeDestination(value) {
@@ -111,6 +124,7 @@ function run(command, args, options = {}) {
111124
const result = cp.spawnSync(command, args, {
112125
encoding: "utf8",
113126
timeout: effectiveTimeout,
127+
maxBuffer: commandMaxBufferBytes,
114128
...options
115129
});
116130

@@ -529,7 +543,8 @@ function buildTestRunnerApp(destination, swiftVersion) {
529543
];
530544
const result = cp.spawnSync("xcodebuild", args, {
531545
encoding: "utf8",
532-
timeout: buildTimeoutMs
546+
timeout: buildTimeoutMs,
547+
maxBuffer: commandMaxBufferBytes
533548
});
534549
if (result.error && result.error.code === "ETIMEDOUT") {
535550
if (result.stdout && result.stdout.trim().length > 0) {
@@ -801,7 +816,10 @@ async function waitForCompletedJunitOrLaunchExit(udid, launchProcess, timeoutMs,
801816
}
802817

803818
if (Date.now() - state.lastActivityAt >= inactivityTimeoutMs) {
804-
return { junitResult: null, launchResult, timedOut: true, inactive: true };
819+
const launchStillRunning = launchProcess.exitCode === null || launchProcess.exitCode === undefined;
820+
if (!enableLiveLogStream || !launchStillRunning) {
821+
return { junitResult: null, launchResult, timedOut: true, inactive: true };
822+
}
805823
}
806824

807825
await sleep(250);
@@ -829,7 +847,7 @@ function collectRecentSimulatorLogs(udid, pid) {
829847
"--style",
830848
"compact",
831849
"--last",
832-
"3m",
850+
simulatorLogLookback,
833851
"--predicate",
834852
predicate
835853
]);

scripts/run-tests-macos.js

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
// - MACOS_TEST_SKIP_BUILD=1 skips xcodebuild app build.
77
// - MACOS_TEST_CLEAN_BUILD=1 deletes derived data before build.
88
// - MACOS_COMMAND_TIMEOUT_MS overrides timeout for build commands (default: 10 minutes).
9+
// - MACOS_COMMAND_MAX_BUFFER_BYTES overrides spawnSync maxBuffer for captured command output (default: 64 MiB).
910
// - MACOS_TEST_TIMEOUT_MS overrides max test runtime after launch (default: 2 minutes).
1011
// - MACOS_TEST_INACTIVITY_TIMEOUT_MS overrides max no-log interval after launch (default: 45 seconds).
1112
// - MACOS_LOG_JUNIT=0 disables streaming TKUnit/JUnit lines to console.
@@ -68,7 +69,17 @@ function parseTimeoutMs(name, fallback) {
6869
return value;
6970
}
7071

72+
function parsePositiveInt(name, fallback) {
73+
const value = Number(process.env[name] || fallback);
74+
if (!Number.isFinite(value) || value <= 0) {
75+
return fallback;
76+
}
77+
78+
return Math.floor(value);
79+
}
80+
7181
const commandTimeoutMs = parseTimeoutMs("MACOS_COMMAND_TIMEOUT_MS", 10 * 60 * 1000);
82+
const commandMaxBufferBytes = parsePositiveInt("MACOS_COMMAND_MAX_BUFFER_BYTES", 64 * 1024 * 1024);
7283
const testTimeoutMs = parseTimeoutMs("MACOS_TEST_TIMEOUT_MS", 2 * 60 * 1000);
7384
const inactivityTimeoutMs = parseTimeoutMs("MACOS_TEST_INACTIVITY_TIMEOUT_MS", 45 * 1000);
7485
const emitJunitLogs = process.env.MACOS_LOG_JUNIT !== "0";
@@ -94,6 +105,7 @@ function run(command, args, options = {}) {
94105
const result = cp.spawnSync(command, args, {
95106
encoding: "utf8",
96107
timeout: effectiveTimeout,
108+
maxBuffer: commandMaxBufferBytes,
97109
...options
98110
});
99111

@@ -350,7 +362,8 @@ function emitLLDBBacktrace(appBinaryPath, runArgs) {
350362

351363
const result = cp.spawnSync("xcrun", args, {
352364
encoding: "utf8",
353-
timeout: commandTimeoutMs
365+
timeout: commandTimeoutMs,
366+
maxBuffer: commandMaxBufferBytes
354367
});
355368

356369
if (result.error) {
@@ -396,7 +409,8 @@ async function emitCrashBacktrace(appBinaryPath, runArgs, launchedAtMs, pid) {
396409
function runBuildAndRequireSuccess(command, args, timeoutMs = commandTimeoutMs) {
397410
const result = cp.spawnSync(command, args, {
398411
encoding: "utf8",
399-
timeout: timeoutMs
412+
timeout: timeoutMs,
413+
maxBuffer: commandMaxBufferBytes
400414
});
401415

402416
if (result.error && result.error.code === "ETIMEDOUT") {

0 commit comments

Comments
 (0)