Skip to content

Commit d8ab730

Browse files
committed
test: livesync-tool initial tests
1 parent d97f1ca commit d8ab730

File tree

5 files changed

+156
-10
lines changed

5 files changed

+156
-10
lines changed

lib/bootstrap.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,7 @@ $injector.require("deployCommandHelper", "./helpers/deploy-command-helper");
123123

124124
$injector.requirePublicClass("localBuildService", "./services/local-build-service");
125125
$injector.requirePublicClass("liveSyncService", "./services/livesync/livesync-service");
126+
$injector.require("LiveSyncSocket", "./services/livesync/livesync-socket");
126127
$injector.requirePublicClass("androidLivesyncTool", "./services/livesync/android-livesync-tool");
127128
$injector.require("androidLiveSyncService", "./services/livesync/android-livesync-service");
128129
$injector.require("iOSLiveSyncService", "./services/livesync/ios-livesync-service");

lib/definitions/livesync.d.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -488,6 +488,10 @@ interface IAndroidLivesyncToolConfiguration {
488488
* If provider will call it when an error occurs.
489489
*/
490490
errorHandler?: any;
491+
/**
492+
* Time to wait for successful connection. Defaults to 30000 miliseconds.
493+
*/
494+
connectTimeout?: number;
491495
}
492496

493497
interface IAndroidLivesyncSyncOperationResult {

lib/services/livesync/android-livesync-tool.ts

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,9 @@
1-
import * as net from "net";
21
import * as path from "path";
32
import * as crypto from "crypto";
43

54
const PROTOCOL_VERSION_LENGTH_SIZE = 1;
65
const PROTOCOL_OPERATION_LENGTH_SIZE = 1;
76
const SIZE_BYTE_LENGTH = 1;
8-
const DELETE_FILE_OPERATION = 7;
9-
const CREATE_FILE_OPERATION = 8;
10-
const DO_SYNC_OPERATION = 9;
117
const ERROR_REPORT = 1;
128
const OPERATION_END_REPORT = 2;
139
const OPERATION_END_NO_REFRESH_REPORT_CODE = 3;
@@ -20,6 +16,9 @@ const TRY_CONNECT_TIMEOUT = 30000;
2016
const DEFAULT_LOCAL_HOST_ADDRESS = "127.0.0.1";
2117

2218
export class AndroidLivesyncTool implements IAndroidLivesyncTool {
19+
public static DELETE_FILE_OPERATION = 7;
20+
public static CREATE_FILE_OPERATION = 8;
21+
public static DO_SYNC_OPERATION = 9;
2322
public protocolVersion: string;
2423
private operationPromises: IDictionary<any>;
2524
private socketError: string | Error;
@@ -37,7 +36,8 @@ export class AndroidLivesyncTool implements IAndroidLivesyncTool {
3736
private $fs: IFileSystem,
3837
private $logger: ILogger,
3938
private $mobileHelper: Mobile.IMobileHelper,
40-
private $processService: IProcessService) {
39+
private $processService: IProcessService,
40+
private $injector: IInjector) {
4141
this.operationPromises = Object.create(null);
4242
this.socketError = null;
4343
this.socketConnection = null;
@@ -61,6 +61,8 @@ export class AndroidLivesyncTool implements IAndroidLivesyncTool {
6161
configuration.localHostAddress = DEFAULT_LOCAL_HOST_ADDRESS;
6262
}
6363

64+
const connectTimeout = configuration.connectTimeout || TRY_CONNECT_TIMEOUT;
65+
6466
this.configuration = configuration;
6567
this.socketError = null;
6668

@@ -70,7 +72,7 @@ export class AndroidLivesyncTool implements IAndroidLivesyncTool {
7072
abstractPort: `localabstract:${configuration.appIdentifier}-livesync`
7173
});
7274

73-
const connectionResult = await this.connectEventuallyUntilTimeout(this.createSocket.bind(this, port), TRY_CONNECT_TIMEOUT);
75+
const connectionResult = await this.connectEventuallyUntilTimeout(this.createSocket.bind(this, port), connectTimeout);
7476
this.handleConnection(connectionResult);
7577
}
7678

@@ -106,7 +108,7 @@ export class AndroidLivesyncTool implements IAndroidLivesyncTool {
106108
filePathData.filePathLengthBytes);
107109

108110
let offset = 0;
109-
offset += headerBuffer.write(DELETE_FILE_OPERATION.toString(), offset, PROTOCOL_OPERATION_LENGTH_SIZE);
111+
offset += headerBuffer.write(AndroidLivesyncTool.DELETE_FILE_OPERATION.toString(), offset, PROTOCOL_OPERATION_LENGTH_SIZE);
110112
offset = headerBuffer.writeInt8(filePathData.filePathLengthSize, offset);
111113
offset += headerBuffer.write(filePathData.filePathLengthString, offset, filePathData.filePathLengthSize);
112114
headerBuffer.write(filePathData.relativeFilePath, offset, filePathData.filePathLengthBytes);
@@ -135,7 +137,7 @@ export class AndroidLivesyncTool implements IAndroidLivesyncTool {
135137
const id = operationId || this.generateOperationIdentifier();
136138
const operationPromise: Promise<IAndroidLivesyncSyncOperationResult> = new Promise((resolve: Function, reject: Function) => {
137139
this.verifyActiveConnection(reject);
138-
const message = `${DO_SYNC_OPERATION}${id}`;
140+
const message = `${AndroidLivesyncTool.DO_SYNC_OPERATION}${id}`;
139141
const headerBuffer = Buffer.alloc(Buffer.byteLength(message) + DO_REFRESH_LENGTH);
140142
const socketId = this.socketConnection.uid;
141143
const doRefreshCode = doRefresh ? DO_REFRESH : SKIP_REFRESH;
@@ -211,7 +213,7 @@ export class AndroidLivesyncTool implements IAndroidLivesyncTool {
211213
}
212214

213215
let offset = 0;
214-
offset += headerBuffer.write(CREATE_FILE_OPERATION.toString(), offset, PROTOCOL_OPERATION_LENGTH_SIZE);
216+
offset += headerBuffer.write(AndroidLivesyncTool.CREATE_FILE_OPERATION.toString(), offset, PROTOCOL_OPERATION_LENGTH_SIZE);
215217
offset = headerBuffer.writeUInt8(filePathData.filePathLengthSize, offset);
216218
offset += headerBuffer.write(filePathData.filePathLengthString, offset, filePathData.filePathLengthSize);
217219
offset += headerBuffer.write(filePathData.relativeFilePath, offset, filePathData.filePathLengthBytes);
@@ -261,7 +263,7 @@ export class AndroidLivesyncTool implements IAndroidLivesyncTool {
261263
}
262264

263265
private createSocket(port: number): INetSocket {
264-
const socket = new net.Socket();
266+
const socket = this.$injector.resolve("LiveSyncSocket");
265267
socket.connect(port, this.configuration.localHostAddress);
266268
return socket;
267269
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import * as net from "net";
2+
3+
class LiveSyncSocket extends net.Socket {
4+
public uid: string
5+
}
6+
7+
$injector.register("LiveSyncSocket", LiveSyncSocket, false);
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
import { Yok } from "../../../lib/common/yok";
2+
import { assert } from "chai";
3+
import * as sinon from "sinon";
4+
import { LoggerStub, ProcessServiceStub } from "../../stubs";
5+
import { AndroidLivesyncTool } from "../../../lib/services/livesync/android-livesync-tool";
6+
import { MobileHelper } from "../../../lib/common/mobile/mobile-helper";
7+
import * as net from "net";
8+
import { Duplex } from "stream";
9+
import { MobilePlatformsCapabilities } from "../../../lib/mobile-platforms-capabilities";
10+
import { DevicePlatformsConstants } from "../../../lib/common/mobile/device-platforms-constants";
11+
12+
const createTestInjector = (socket: INetSocket): IInjector => {
13+
const testInjector = new Yok();
14+
15+
testInjector.register("fs", {
16+
createReadStream: (filename: string, encoding?: IReadFileOptions | string): NodeJS.ReadableStream => {
17+
const fileStream = new Duplex();
18+
fileStream.write("testContent");
19+
fileStream.end();
20+
return fileStream;
21+
},
22+
23+
exists: (): boolean => true,
24+
25+
readJson: (filePath: string): any => null,
26+
27+
enumerateFilesInDirectorySync: (directoryPath: string,
28+
filterCallback?: (_file: string, _stat: IFsStats) => boolean,
29+
opts?: { enumerateDirectories?: boolean, includeEmptyDirectories?: boolean },
30+
foundFiles?: string[]): string[] => []
31+
});
32+
33+
testInjector.register("logger", LoggerStub);
34+
testInjector.register("processService", ProcessServiceStub);
35+
testInjector.register("injector", testInjector);
36+
testInjector.register("mobileHelper", MobileHelper);
37+
testInjector.register("androidProcessService", {
38+
forwardFreeTcpToAbstractPort: () => Promise.resolve("")
39+
});
40+
testInjector.register("LiveSyncSocket", () => { return socket} );
41+
testInjector.register("mobilePlatformsCapabilities", MobilePlatformsCapabilities);
42+
testInjector.register("devicePlatformsConstants", DevicePlatformsConstants);
43+
testInjector.register("errors", {
44+
fail: (message: string) => {
45+
throw new Error(message);
46+
}
47+
});
48+
49+
return testInjector;
50+
};
51+
52+
const getHandshakeBuffer = () => {
53+
const protocolVersion = "0.2.0";
54+
const packageName = "org.comp.test";
55+
const handshakeBuffer = Buffer.alloc(Buffer.byteLength(protocolVersion) + Buffer.byteLength(packageName) + 1);
56+
57+
let offset = handshakeBuffer.writeUInt8(Buffer.byteLength(protocolVersion), 0);
58+
offset =+ handshakeBuffer.write(protocolVersion, offset);
59+
handshakeBuffer.write(packageName, offset);
60+
61+
return handshakeBuffer;
62+
}
63+
64+
describe.only("AndroidLivesyncTool", () => {
65+
let testInjector: IInjector = null;
66+
let livesyncTool: IAndroidLivesyncTool = null;
67+
let testSocket: INetSocket;
68+
let sandbox: sinon.SinonSandbox = null;
69+
70+
beforeEach(() => {
71+
sandbox = sinon.sandbox.create();
72+
testSocket = new net.Socket();
73+
var stub = sandbox.stub(testSocket, 'write').callsFake(function () {
74+
75+
var args = stub.args;
76+
77+
// this will echo whatever they wrote
78+
if (args.length > 0)
79+
testSocket.emit('data', stub.args[stub.callCount - 1][0]);
80+
});
81+
testInjector = createTestInjector(testSocket);
82+
livesyncTool = testInjector.resolve(AndroidLivesyncTool);
83+
});
84+
85+
afterEach(() => {
86+
sandbox.restore();
87+
});
88+
89+
describe("methods", () => {
90+
describe("connect", () => {
91+
it("should retry if first connect fails", () => {
92+
const originalOn = testSocket.on;
93+
const originalOnce = testSocket.once;
94+
let dataAttachCount = 0;
95+
let closeAttachCount = 0;
96+
sandbox.stub(testSocket, "on").callsFake(function(event: string) {
97+
originalOn.apply(this, arguments);
98+
if(event === "close") {
99+
closeAttachCount++;
100+
if(closeAttachCount === 1){
101+
testSocket.emit("close", false);
102+
}
103+
}
104+
});
105+
106+
sandbox.stub(testSocket, "once").callsFake(function(event: string) {
107+
originalOnce.apply(this, arguments);
108+
if(event === "data") {
109+
dataAttachCount++
110+
if(dataAttachCount === 2){
111+
testSocket.write(getHandshakeBuffer());
112+
}
113+
}
114+
});
115+
const connectStub: sinon.SinonStub = sandbox.stub(testSocket, "connect");
116+
const connectPromise = livesyncTool.connect({ appIdentifier: "test", deviceIdentifier: "test", appPlatformsPath: "test" });
117+
118+
return connectPromise.then(() => {
119+
assert(connectStub.calledTwice);
120+
}
121+
);
122+
});
123+
124+
it("should fail eventually", () => {
125+
sandbox.stub(testSocket, "connect");
126+
const connectPromise = livesyncTool.connect({ appIdentifier: "test", deviceIdentifier: "test", appPlatformsPath: "test", connectTimeout: 400 });
127+
128+
return connectPromise.should.be.rejected;
129+
});
130+
});
131+
});
132+
});

0 commit comments

Comments
 (0)