mirror of
https://github.com/balena-io/balena-cli.git
synced 2025-02-20 09:26:42 +00:00
Add more tests for push/build/deploy commands (--convert-eol)
Change-type: patch Signed-off-by: Paulo Castro <paulo@balena.io>
This commit is contained in:
parent
0f5f65e0d3
commit
f9743b269a
@ -58,7 +58,7 @@ async function detectEncoding(data: Buffer): Promise<string> {
|
||||
* buffer size.
|
||||
* @param buf
|
||||
*/
|
||||
function convertEolInPlace(buf: Buffer): Buffer {
|
||||
export function convertEolInPlace(buf: Buffer): Buffer {
|
||||
const CR = 13;
|
||||
const LF = 10;
|
||||
let foundCR = false;
|
||||
|
@ -27,6 +27,7 @@ import { BalenaAPIMock } from '../balena-api-mock';
|
||||
import { DockerMock, dockerResponsePath } from '../docker-mock';
|
||||
import {
|
||||
cleanOutput,
|
||||
expectStreamNoCRLF,
|
||||
inspectTarStream,
|
||||
runCommand,
|
||||
TarStreamFiles,
|
||||
@ -73,11 +74,12 @@ describe('balena build', function() {
|
||||
docker.done();
|
||||
});
|
||||
|
||||
it('should create the expected tar stream', async () => {
|
||||
it('should create the expected tar stream (single container)', async () => {
|
||||
const projectPath = path.join(projectsPath, 'no-docker-compose', 'basic');
|
||||
const expectedFiles: TarStreamFiles = {
|
||||
'src/start.sh': { fileSize: 89, type: 'file' },
|
||||
Dockerfile: { fileSize: 85, type: 'file' },
|
||||
'src/windows-crlf.sh': { fileSize: 70, type: 'file' },
|
||||
Dockerfile: { fileSize: 88, type: 'file' },
|
||||
'Dockerfile-alt': { fileSize: 30, type: 'file' },
|
||||
};
|
||||
const responseFilename = 'build-POST.json';
|
||||
@ -109,6 +111,60 @@ describe('balena build', function() {
|
||||
).to.include.members([
|
||||
`[Info] Creating default composition with source: ${projectPath}`,
|
||||
...expectedResponses[responseFilename],
|
||||
`[Warn] CRLF (Windows) line endings detected in file: ${path.join(
|
||||
projectPath,
|
||||
'src',
|
||||
'windows-crlf.sh',
|
||||
)}`,
|
||||
]);
|
||||
});
|
||||
|
||||
it('should create the expected tar stream (single container, --convert-eol)', async () => {
|
||||
const projectPath = path.join(projectsPath, 'no-docker-compose', 'basic');
|
||||
const expectedFiles: TarStreamFiles = {
|
||||
'src/start.sh': { fileSize: 89, type: 'file' },
|
||||
'src/windows-crlf.sh': {
|
||||
fileSize: 68,
|
||||
type: 'file',
|
||||
testStream: expectStreamNoCRLF,
|
||||
},
|
||||
Dockerfile: { fileSize: 88, type: 'file' },
|
||||
'Dockerfile-alt': { fileSize: 30, type: 'file' },
|
||||
};
|
||||
const responseFilename = 'build-POST.json';
|
||||
const responseBody = await fs.readFile(
|
||||
path.join(dockerResponsePath, responseFilename),
|
||||
'utf8',
|
||||
);
|
||||
|
||||
docker.expectPostBuild({
|
||||
tag: 'basic_main',
|
||||
responseCode: 200,
|
||||
responseBody,
|
||||
checkURI: async (uri: string) => {
|
||||
const url = new URL(uri, 'http://test.net/');
|
||||
const queryParams = Array.from(url.searchParams.entries());
|
||||
expect(queryParams).to.have.deep.members(commonQueryParams);
|
||||
},
|
||||
checkBuildRequestBody: (buildRequestBody: string) =>
|
||||
inspectTarStream(buildRequestBody, expectedFiles, projectPath, expect),
|
||||
});
|
||||
|
||||
const { out, err } = await runCommand(
|
||||
`build ${projectPath} --deviceType nuc --arch amd64 --convert-eol`,
|
||||
);
|
||||
|
||||
expect(err).to.have.members([]);
|
||||
expect(
|
||||
cleanOutput(out).map(line => line.replace(/\s{2,}/g, ' ')),
|
||||
).to.include.members([
|
||||
`[Info] Creating default composition with source: ${projectPath}`,
|
||||
`[Info] Converting line endings CRLF -> LF for file: ${path.join(
|
||||
projectPath,
|
||||
'src',
|
||||
'windows-crlf.sh',
|
||||
)}`,
|
||||
...expectedResponses[responseFilename],
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
@ -93,11 +93,12 @@ describe('balena deploy', function() {
|
||||
docker.done();
|
||||
});
|
||||
|
||||
it('should create the expected --build tar stream', async () => {
|
||||
it('should create the expected --build tar stream (single container)', async () => {
|
||||
const projectPath = path.join(projectsPath, 'no-docker-compose', 'basic');
|
||||
const expectedFiles: TarStreamFiles = {
|
||||
'src/start.sh': { fileSize: 89, type: 'file' },
|
||||
Dockerfile: { fileSize: 85, type: 'file' },
|
||||
'src/windows-crlf.sh': { fileSize: 70, type: 'file' },
|
||||
Dockerfile: { fileSize: 88, type: 'file' },
|
||||
'Dockerfile-alt': { fileSize: 30, type: 'file' },
|
||||
};
|
||||
const responseFilename = 'build-POST.json';
|
||||
@ -129,6 +130,11 @@ describe('balena deploy', function() {
|
||||
).to.include.members([
|
||||
`[Info] Creating default composition with source: ${projectPath}`,
|
||||
...expectedResponses[responseFilename],
|
||||
`[Warn] CRLF (Windows) line endings detected in file: ${path.join(
|
||||
projectPath,
|
||||
'src',
|
||||
'windows-crlf.sh',
|
||||
)}`,
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
@ -27,6 +27,7 @@ import { BalenaAPIMock } from '../balena-api-mock';
|
||||
import { BuilderMock, builderResponsePath } from '../builder-mock';
|
||||
import {
|
||||
cleanOutput,
|
||||
expectStreamNoCRLF,
|
||||
inspectTarStream,
|
||||
runCommand,
|
||||
TarStreamFiles,
|
||||
@ -106,7 +107,8 @@ describe('balena push', function() {
|
||||
const projectPath = path.join(projectsPath, 'no-docker-compose', 'basic');
|
||||
const expectedFiles: TarStreamFiles = {
|
||||
'src/start.sh': { fileSize: 89, type: 'file' },
|
||||
Dockerfile: { fileSize: 85, type: 'file' },
|
||||
'src/windows-crlf.sh': { fileSize: 70, type: 'file' },
|
||||
Dockerfile: { fileSize: 88, type: 'file' },
|
||||
'Dockerfile-alt': { fileSize: 30, type: 'file' },
|
||||
};
|
||||
const responseFilename = 'build-POST-v3.json';
|
||||
@ -132,16 +134,22 @@ describe('balena push', function() {
|
||||
);
|
||||
|
||||
expect(err).to.have.members([]);
|
||||
expect(tweakOutput(out)).to.include.members(
|
||||
expectedResponses[responseFilename],
|
||||
);
|
||||
expect(tweakOutput(out)).to.include.members([
|
||||
...expectedResponses[responseFilename],
|
||||
`[Warn] CRLF (Windows) line endings detected in file: ${path.join(
|
||||
projectPath,
|
||||
'src',
|
||||
'windows-crlf.sh',
|
||||
)}`,
|
||||
]);
|
||||
});
|
||||
|
||||
it('should create the expected tar stream (alternative Dockerfile)', async () => {
|
||||
const projectPath = path.join(projectsPath, 'no-docker-compose', 'basic');
|
||||
const expectedFiles: TarStreamFiles = {
|
||||
'src/start.sh': { fileSize: 89, type: 'file' },
|
||||
Dockerfile: { fileSize: 85, type: 'file' },
|
||||
'src/windows-crlf.sh': { fileSize: 70, type: 'file' },
|
||||
Dockerfile: { fileSize: 88, type: 'file' },
|
||||
'Dockerfile-alt': { fileSize: 30, type: 'file' },
|
||||
};
|
||||
const responseFilename = 'build-POST-v3.json';
|
||||
@ -177,4 +185,49 @@ describe('balena push', function() {
|
||||
expectedResponses[responseFilename],
|
||||
);
|
||||
});
|
||||
|
||||
it('should create the expected tar stream (single container, --convert-eol)', async () => {
|
||||
const projectPath = path.join(projectsPath, 'no-docker-compose', 'basic');
|
||||
const expectedFiles: TarStreamFiles = {
|
||||
'src/start.sh': { fileSize: 89, type: 'file' },
|
||||
'src/windows-crlf.sh': {
|
||||
fileSize: 68,
|
||||
type: 'file',
|
||||
testStream: expectStreamNoCRLF,
|
||||
},
|
||||
Dockerfile: { fileSize: 88, type: 'file' },
|
||||
'Dockerfile-alt': { fileSize: 30, type: 'file' },
|
||||
};
|
||||
const responseFilename = 'build-POST-v3.json';
|
||||
const responseBody = await fs.readFile(
|
||||
path.join(builderResponsePath, responseFilename),
|
||||
'utf8',
|
||||
);
|
||||
|
||||
builder.expectPostBuild({
|
||||
responseCode: 200,
|
||||
responseBody,
|
||||
checkURI: async (uri: string) => {
|
||||
const url = new URL(uri, 'http://test.net/');
|
||||
const queryParams = Array.from(url.searchParams.entries());
|
||||
expect(queryParams).to.have.deep.members(commonQueryParams);
|
||||
},
|
||||
checkBuildRequestBody: (buildRequestBody: string | Buffer) =>
|
||||
inspectTarStream(buildRequestBody, expectedFiles, projectPath, expect),
|
||||
});
|
||||
|
||||
const { out, err } = await runCommand(
|
||||
`push testApp --source ${projectPath} --convert-eol`,
|
||||
);
|
||||
|
||||
expect(err).to.have.members([]);
|
||||
expect(tweakOutput(out)).to.include.members([
|
||||
...expectedResponses[responseFilename],
|
||||
`[Info] Converting line endings CRLF -> LF for file: ${path.join(
|
||||
projectPath,
|
||||
'src',
|
||||
'windows-crlf.sh',
|
||||
)}`,
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
@ -18,6 +18,7 @@
|
||||
// tslint:disable-next-line:no-var-requires
|
||||
require('./config-tests'); // required for side effects
|
||||
|
||||
import { stripIndent } from 'common-tags';
|
||||
import intercept = require('intercept-stdout');
|
||||
import * as _ from 'lodash';
|
||||
import { fs } from 'mz';
|
||||
@ -117,6 +118,7 @@ export interface TarStreamFiles {
|
||||
[filePath: string]: {
|
||||
fileSize: number;
|
||||
type: tar.Headers['type'];
|
||||
testStream?: (header: tar.Headers, stream: Readable) => Promise<void>;
|
||||
};
|
||||
}
|
||||
|
||||
@ -159,13 +161,12 @@ export async function inspectTarStream(
|
||||
fileSize: header.size || 0,
|
||||
type: header.type,
|
||||
};
|
||||
const [buf, buf2] = await Promise.all([
|
||||
streamToBuffer(stream),
|
||||
fs.readFile(
|
||||
path.join(projectPath, PathUtils.toNativePath(header.name)),
|
||||
),
|
||||
]);
|
||||
expect(buf.equals(buf2)).to.be.true;
|
||||
const expected = expectedFiles[header.name];
|
||||
if (expected && expected.testStream) {
|
||||
await expected.testStream(header, stream);
|
||||
} else {
|
||||
await defaultTestStream(header, stream, projectPath, expect);
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
reject(err);
|
||||
@ -180,5 +181,34 @@ export async function inspectTarStream(
|
||||
sourceTarStream.pipe(extract);
|
||||
});
|
||||
|
||||
expect(found).to.deep.equal(expectedFiles);
|
||||
expect(found).to.deep.equal(
|
||||
_.mapValues(expectedFiles, v => _.omit(v, 'testStream')),
|
||||
);
|
||||
}
|
||||
|
||||
/** Check that a tar stream entry matches the project contents in the filesystem */
|
||||
async function defaultTestStream(
|
||||
header: tar.Headers,
|
||||
stream: Readable,
|
||||
projectPath: string,
|
||||
expect: Chai.ExpectStatic,
|
||||
): Promise<void> {
|
||||
const [buf, buf2] = await Promise.all([
|
||||
streamToBuffer(stream),
|
||||
fs.readFile(path.join(projectPath, PathUtils.toNativePath(header.name))),
|
||||
]);
|
||||
const msg = stripIndent`
|
||||
contents mismatch for tar stream entry "${header.name}"
|
||||
stream length=${buf.length}, filesystem length=${buf2.length}`;
|
||||
expect(buf.equals(buf2), msg).to.be.true;
|
||||
}
|
||||
|
||||
/** Test a tar stream entry for the absence of Windows CRLF line breaks */
|
||||
export async function expectStreamNoCRLF(
|
||||
_header: tar.Headers,
|
||||
stream: Readable,
|
||||
): Promise<void> {
|
||||
const chai = await import('chai');
|
||||
const buf = await streamToBuffer(stream);
|
||||
await chai.expect(buf.includes('\r\n')).to.be.false;
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
FROM busybox
|
||||
COPY ./src/start.sh /start.sh
|
||||
RUN chmod a+x /start.sh
|
||||
CMD ["/start.sh"]
|
||||
COPY ./src /usr/src/
|
||||
RUN chmod a+x /usr/src/*.sh
|
||||
CMD ["/usr/src/start.sh"]
|
||||
|
@ -0,0 +1,2 @@
|
||||
#!/bin/sh
|
||||
echo 'this file was saved with Windows CRLF line endings'
|
55
tests/utils/eol-conversion.spec.ts
Normal file
55
tests/utils/eol-conversion.spec.ts
Normal file
@ -0,0 +1,55 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2020 Balena Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { expect } from 'chai';
|
||||
|
||||
import { convertEolInPlace } from '../../build/utils/eol-conversion';
|
||||
|
||||
describe('convertEolInPlace() function', function() {
|
||||
it('should return expected values', () => {
|
||||
// pairs of [given input, expected output]
|
||||
const testVector = [
|
||||
['', ''],
|
||||
['\r', '\r'],
|
||||
['\n', '\n'],
|
||||
['\r\r', '\r\r'],
|
||||
['\n\r', '\n\r'],
|
||||
['\r\n', '\n'],
|
||||
['\r\n\n', '\n\n'],
|
||||
['\r\n\r', '\n\r'],
|
||||
['\r\n\r\n', '\n\n'],
|
||||
['\r\n\n\r', '\n\n\r'],
|
||||
['abc\r\ndef\r\n', 'abc\ndef\n'],
|
||||
['abc\r\ndef\n\r', 'abc\ndef\n\r'],
|
||||
['abc\r\ndef\n', 'abc\ndef\n'],
|
||||
['abc\r\ndef\r', 'abc\ndef\r'],
|
||||
['abc\r\ndef', 'abc\ndef'],
|
||||
['\r\ndef\r\n', '\ndef\n'],
|
||||
['\rdef\r', '\rdef\r'],
|
||||
];
|
||||
const js = JSON.stringify;
|
||||
|
||||
for (const [input, expected] of testVector) {
|
||||
const result = convertEolInPlace(Buffer.from(input));
|
||||
const resultStr = result.toString();
|
||||
const msg = `input=${js(input)} result=${js(resultStr)} expected=${js(
|
||||
expected,
|
||||
)}`;
|
||||
expect(resultStr).to.equal(expected, msg);
|
||||
}
|
||||
});
|
||||
});
|
Loading…
x
Reference in New Issue
Block a user