From f27e0a13ef8d308cbb78512ca712d6308d937190 Mon Sep 17 00:00:00 2001
From: Przemyslaw Zan
Date: Tue, 31 Jan 2023 16:57:17 +0100
Subject: [PATCH 1/2] Fixed errors caused by repositories without tags.
---
lib/commands/status.js | 50 +++++++++++++++----------------
lib/commands/sync.js | 5 ++++
tests/commands/status.js | 65 ++++++++++++++++++++++++++++++++--------
tests/commands/sync.js | 40 ++++++++++++++++++++++++-
4 files changed, 122 insertions(+), 38 deletions(-)
diff --git a/lib/commands/status.js b/lib/commands/status.js
index efb556e..b1e88e6 100644
--- a/lib/commands/status.js
+++ b/lib/commands/status.js
@@ -37,38 +37,38 @@ module.exports = {
* @param {CommandData} data
* @returns {Promise}
*/
- execute( data ) {
+ async execute( data ) {
const execCommand = require( './exec' );
- const promises = [
- execCommand.execute( getExecData( 'git rev-parse HEAD' ) ),
- execCommand.execute( getExecData( 'git status --branch --porcelain' ) ),
- execCommand.execute( getExecData( 'git describe --abbrev=0 --tags' ) ),
- execCommand.execute( getExecData( 'git log --tags --simplify-by-decoration --pretty="%S"' ) )
- ];
+ let latestTag = null;
+ let currentTag = null;
+ let packageName = data.packageName;
- return Promise.all( promises )
- .then( ( [ hashResponse, currentBranchStatusResponse, currentTagStatusResponse, latestTagStatusResponse ] ) => {
- let packageName = data.packageName;
+ const hashResponse = await execCommand.execute( getExecData( 'git rev-parse HEAD' ) );
+ const currentBranchStatusResponse = await execCommand.execute( getExecData( 'git status --branch --porcelain' ) );
+ const latestTagStatusResponse = await execCommand.execute( getExecData( 'git log --tags --simplify-by-decoration --pretty="%S"' ) );
- const currentTag = currentTagStatusResponse.logs.info[ 0 ];
- const latestTag = latestTagStatusResponse.logs.info[ 0 ].trim().split( '\n' ).shift();
+ if ( latestTagStatusResponse.logs.info.length ) {
+ const currentTagStatusResponse = await execCommand.execute( getExecData( 'git describe --abbrev=0 --tags' ) );
- for ( const packagePrefix of data.toolOptions.packagesPrefix ) {
- packageName = packageName.replace( new RegExp( '^' + packagePrefix ), '' );
- }
+ latestTag = latestTagStatusResponse.logs.info[ 0 ].trim().split( '\n' ).shift();
+ currentTag = currentTagStatusResponse.logs.info[ 0 ];
+ }
- const commandResponse = {
- packageName,
- status: gitStatusParser( currentBranchStatusResponse.logs.info[ 0 ], currentTag ),
- commit: hashResponse.logs.info[ 0 ].slice( 0, 7 ), // Short version of the commit hash.
- mrgitBranch: data.repository.branch,
- mrgitTag: data.repository.tag,
- latestTag
- };
+ for ( const packagePrefix of data.toolOptions.packagesPrefix ) {
+ packageName = packageName.replace( new RegExp( '^' + packagePrefix ), '' );
+ }
- return { response: commandResponse };
- } );
+ const commandResponse = {
+ packageName,
+ status: gitStatusParser( currentBranchStatusResponse.logs.info[ 0 ], currentTag ),
+ commit: hashResponse.logs.info[ 0 ].slice( 0, 7 ), // Short version of the commit hash.
+ mrgitBranch: data.repository.branch,
+ mrgitTag: data.repository.tag,
+ latestTag
+ };
+
+ return { response: commandResponse };
function getExecData( command ) {
return Object.assign( {}, data, {
diff --git a/lib/commands/sync.js b/lib/commands/sync.js
index bf582da..09ffc27 100644
--- a/lib/commands/sync.js
+++ b/lib/commands/sync.js
@@ -83,6 +83,11 @@ module.exports = {
const commandOutput = await execCommand.execute(
getExecData( 'git log --tags --simplify-by-decoration --pretty="%S"' )
);
+
+ if ( !commandOutput.logs.info.length ) {
+ throw new Error( `Can't check out the latest tag as package "${ data.packageName }" has no tags. Aborted.` );
+ }
+
const latestTag = commandOutput.logs.info[ 0 ].trim().split( '\n' ).shift();
checkoutValue = 'tags/' + latestTag.trim();
diff --git a/tests/commands/status.js b/tests/commands/status.js
index 85ac4c3..5f8ebef 100644
--- a/tests/commands/status.js
+++ b/tests/commands/status.js
@@ -142,10 +142,10 @@ describe( 'commands/status', () => {
logs: { info: [ 'Response returned by "git status" command.' ] }
} );
stubs.execCommand.execute.onCall( 2 ).resolves( {
- logs: { info: [ 'Response returned by "git describe" command.' ] }
+ logs: { info: [ '\nv35.3.2\nv35.3.1\nv35.3.0\nv35.2.1\nv35.2.0' ] }
} );
stubs.execCommand.execute.onCall( 3 ).resolves( {
- logs: { info: [ '\nv35.3.2\nv35.3.1\nv35.3.0\nv35.2.1\nv35.2.0' ] }
+ logs: { info: [ 'Response returned by "git describe" command.' ] }
} );
stubs.gitStatusParser.returns( { response: 'Parsed response.' } );
@@ -160,10 +160,10 @@ describe( 'commands/status', () => {
getCommandArguments( 'git status --branch --porcelain' )
);
expect( stubs.execCommand.execute.getCall( 2 ).args[ 0 ] ).to.deep.equal(
- getCommandArguments( 'git describe --abbrev=0 --tags' )
+ getCommandArguments( 'git log --tags --simplify-by-decoration --pretty="%S"' )
);
expect( stubs.execCommand.execute.getCall( 3 ).args[ 0 ] ).to.deep.equal(
- getCommandArguments( 'git log --tags --simplify-by-decoration --pretty="%S"' )
+ getCommandArguments( 'git describe --abbrev=0 --tags' )
);
expect( stubs.gitStatusParser.calledOnce ).to.equal( true );
@@ -181,6 +181,47 @@ describe( 'commands/status', () => {
} );
} );
+ it( 'works properly for repositories without tags', () => {
+ stubs.execCommand.execute.onCall( 0 ).resolves( {
+ logs: { info: [ '6bfd379a56a32c9f8b6e58bf08e39c124cdbae10' ] }
+ } );
+ stubs.execCommand.execute.onCall( 1 ).resolves( {
+ logs: { info: [ 'Response returned by "git status" command.' ] }
+ } );
+ stubs.execCommand.execute.onCall( 2 ).resolves( {
+ logs: { info: [] }
+ } );
+
+ stubs.gitStatusParser.returns( { response: 'Parsed response.' } );
+
+ return statusCommand.execute( commandData )
+ .then( statusResponse => {
+ expect( stubs.execCommand.execute.callCount ).to.equal( 3 );
+ expect( stubs.execCommand.execute.getCall( 0 ).args[ 0 ] ).to.deep.equal(
+ getCommandArguments( 'git rev-parse HEAD' )
+ );
+ expect( stubs.execCommand.execute.getCall( 1 ).args[ 0 ] ).to.deep.equal(
+ getCommandArguments( 'git status --branch --porcelain' )
+ );
+ expect( stubs.execCommand.execute.getCall( 2 ).args[ 0 ] ).to.deep.equal(
+ getCommandArguments( 'git log --tags --simplify-by-decoration --pretty="%S"' )
+ );
+
+ expect( stubs.gitStatusParser.calledOnce ).to.equal( true );
+ expect( stubs.gitStatusParser.firstCall.args[ 0 ] ).to.equal( 'Response returned by "git status" command.' );
+ expect( stubs.gitStatusParser.firstCall.args[ 1 ] ).to.equal( null );
+
+ expect( statusResponse.response ).to.deep.equal( {
+ packageName: 'test-package',
+ status: { response: 'Parsed response.' },
+ commit: '6bfd379',
+ mrgitBranch: 'master',
+ mrgitTag: undefined,
+ latestTag: null
+ } );
+ } );
+ } );
+
it( 'modifies the package name if "packagesPrefix" is an array', () => {
commandData.toolOptions.packagesPrefix = [
'@ckeditor/ckeditor-',
@@ -194,10 +235,10 @@ describe( 'commands/status', () => {
logs: { info: [ 'Response returned by "git status" command.' ] }
} );
stubs.execCommand.execute.onCall( 2 ).resolves( {
- logs: { info: [ 'Response returned by "git describe" command.' ] }
+ logs: { info: [ 'v35.3.2\nv35.3.1\nv35.3.0\nv35.2.1\nv35.2.0\n' ] }
} );
stubs.execCommand.execute.onCall( 3 ).resolves( {
- logs: { info: [ 'v35.3.2\nv35.3.1\nv35.3.0\nv35.2.1\nv35.2.0\n' ] }
+ logs: { info: [ 'Response returned by "git describe" command.' ] }
} );
stubs.gitStatusParser.returns( { response: 'Parsed response.' } );
@@ -212,10 +253,10 @@ describe( 'commands/status', () => {
getCommandArguments( 'git status --branch --porcelain' )
);
expect( stubs.execCommand.execute.getCall( 2 ).args[ 0 ] ).to.deep.equal(
- getCommandArguments( 'git describe --abbrev=0 --tags' )
+ getCommandArguments( 'git log --tags --simplify-by-decoration --pretty="%S"' )
);
expect( stubs.execCommand.execute.getCall( 3 ).args[ 0 ] ).to.deep.equal(
- getCommandArguments( 'git log --tags --simplify-by-decoration --pretty="%S"' )
+ getCommandArguments( 'git describe --abbrev=0 --tags' )
);
expect( stubs.gitStatusParser.calledOnce ).to.equal( true );
@@ -244,10 +285,10 @@ describe( 'commands/status', () => {
logs: { info: [ 'Response returned by "git status" command.' ] }
} );
stubs.execCommand.execute.onCall( 2 ).resolves( {
- logs: { info: [ 'Response returned by "git describe" command.' ] }
+ logs: { info: [ '\nv35.3.2\nv35.3.1\nv35.3.0\nv35.2.1\nv35.2.0' ] }
} );
stubs.execCommand.execute.onCall( 3 ).resolves( {
- logs: { info: [ '\nv35.3.2\nv35.3.1\nv35.3.0\nv35.2.1\nv35.2.0' ] }
+ logs: { info: [ 'Response returned by "git describe" command.' ] }
} );
stubs.gitStatusParser.returns( { response: 'Parsed response.' } );
@@ -262,10 +303,10 @@ describe( 'commands/status', () => {
getCommandArguments( 'git status --branch --porcelain' )
);
expect( stubs.execCommand.execute.getCall( 2 ).args[ 0 ] ).to.deep.equal(
- getCommandArguments( 'git describe --abbrev=0 --tags' )
+ getCommandArguments( 'git log --tags --simplify-by-decoration --pretty="%S"' )
);
expect( stubs.execCommand.execute.getCall( 3 ).args[ 0 ] ).to.deep.equal(
- getCommandArguments( 'git log --tags --simplify-by-decoration --pretty="%S"' )
+ getCommandArguments( 'git describe --abbrev=0 --tags' )
);
expect( stubs.gitStatusParser.calledOnce ).to.equal( true );
diff --git a/tests/commands/sync.js b/tests/commands/sync.js
index 74fffb6..62c7af7 100644
--- a/tests/commands/sync.js
+++ b/tests/commands/sync.js
@@ -466,6 +466,44 @@ describe( 'commands/sync', () => {
} );
} );
+ it( 'throws an error when trying to check out the latest tag in repository without tags', () => {
+ commandData.repository.tag = 'latest';
+
+ stubs.fs.existsSync.returns( true );
+
+ const exec = stubs.execCommand.execute;
+
+ exec.onCall( 0 ).returns( Promise.resolve( {
+ logs: getCommandLogs( '' )
+ } ) );
+
+ exec.onCall( 1 ).returns( Promise.resolve( {
+ logs: getCommandLogs( '' )
+ } ) );
+
+ exec.onCall( 2 ).returns( Promise.resolve( {
+ logs: getCommandLogs()
+ } ) );
+
+ return syncCommand.execute( commandData )
+ .then( () => {
+ throw new Error( 'Expected to throw' );
+ } )
+ .catch( response => {
+ expect( exec.getCall( 0 ).args[ 0 ].arguments[ 0 ] ).to.equal( 'git status -s' );
+ expect( exec.getCall( 1 ).args[ 0 ].arguments[ 0 ] ).to.equal( 'git fetch' );
+ expect( exec.getCall( 2 ).args[ 0 ].arguments[ 0 ] ).to.equal(
+ 'git log --tags --simplify-by-decoration --pretty="%S"'
+ );
+
+ expect( exec.callCount ).to.equal( 3 );
+
+ expect( response.logs.error[ 0 ] ).to.equal(
+ 'Can\'t check out the latest tag as package "test-package" has no tags. Aborted.'
+ );
+ } );
+ } );
+
it( 'aborts if package has uncommitted changes', () => {
stubs.fs.existsSync.returns( true );
@@ -758,7 +796,7 @@ describe( 'commands/sync', () => {
if ( isError ) {
logs.error.push( msg );
- } else {
+ } else if ( msg ) {
logs.info.push( msg );
}
From db91a4e50820b1ce00a8b9add091337899119933 Mon Sep 17 00:00:00 2001
From: Przemyslaw Zan
Date: Tue, 31 Jan 2023 16:59:40 +0100
Subject: [PATCH 2/2] Code coverage 100%.
---
lib/utils/log.js | 1 -
tests/utils/log.js | 199 +++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 199 insertions(+), 1 deletion(-)
create mode 100644 tests/utils/log.js
diff --git a/lib/utils/log.js b/lib/utils/log.js
index cdd7c85..3f8b805 100644
--- a/lib/utils/log.js
+++ b/lib/utils/log.js
@@ -31,7 +31,6 @@ module.exports = function log() {
msg = msg.trim();
- /* istanbul ignore if */
if ( !msg ) {
return;
}
diff --git a/tests/utils/log.js b/tests/utils/log.js
new file mode 100644
index 0000000..1cb3ba1
--- /dev/null
+++ b/tests/utils/log.js
@@ -0,0 +1,199 @@
+/**
+ * @license Copyright (c) 2003-2023, CKSource Holding sp. z o.o. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+/* jshint mocha:true */
+
+'use strict';
+
+const sinon = require( 'sinon' );
+const expect = require( 'chai' ).expect;
+
+describe( 'utils/log', () => {
+ let log, stubs;
+
+ beforeEach( () => {
+ log = require( '../../lib/utils/log' );
+
+ stubs = {
+ info: sinon.stub(),
+ error: sinon.stub(),
+ log: sinon.stub()
+ };
+ } );
+
+ afterEach( () => {
+ sinon.restore();
+ } );
+
+ describe( 'log()', () => {
+ it( 'returns the logger', () => {
+ const logger = log();
+
+ expect( logger ).to.be.an( 'object' );
+ expect( logger.info ).to.be.a( 'function' );
+ expect( logger.error ).to.be.a( 'function' );
+ expect( logger.log ).to.be.a( 'function' );
+ expect( logger.concat ).to.be.a( 'function' );
+ expect( logger.all ).to.be.a( 'function' );
+ } );
+ } );
+
+ describe( 'logger', () => {
+ let logger;
+
+ beforeEach( () => {
+ logger = log();
+ } );
+
+ describe( 'info()', () => {
+ it( 'calls the log() function with the received message and the type set to "info"', () => {
+ logger.log = stubs.log;
+
+ logger.info( 'Info message.' );
+
+ expect( stubs.log.callCount ).to.equal( 1 );
+ expect( stubs.log.getCall( 0 ).args[ 0 ] ).to.equal( 'info' );
+ expect( stubs.log.getCall( 0 ).args[ 1 ] ).to.equal( 'Info message.' );
+ } );
+ } );
+
+ describe( 'error()', () => {
+ it( 'calls the log() function with the received message and the type set to "error"', () => {
+ logger.log = stubs.log;
+
+ logger.error( 'Error message.' );
+
+ expect( stubs.log.callCount ).to.equal( 1 );
+ expect( stubs.log.getCall( 0 ).args[ 0 ] ).to.equal( 'error' );
+ expect( stubs.log.getCall( 0 ).args[ 1 ] ).to.equal( 'Error message.' );
+ } );
+
+ it( 'calls the log() function with the stack trace of the received error and the type set to "error"', () => {
+ logger.log = stubs.log;
+
+ const errorStack = [
+ '-Error: Error message.',
+ '- at foo (path/to/foo.js:10:20)',
+ '- at bar (path/to/bar.js:30:40)'
+ ].join( '\n' );
+
+ const error = new Error( 'Error message.' );
+ error.stack = errorStack;
+
+ logger.error( error );
+
+ expect( stubs.log.callCount ).to.equal( 1 );
+ expect( stubs.log.getCall( 0 ).args[ 0 ] ).to.equal( 'error' );
+ expect( stubs.log.getCall( 0 ).args[ 1 ] ).to.equal( errorStack );
+ } );
+ } );
+
+ describe( 'log()', () => {
+ it( 'stores messages of the "info" type', () => {
+ expect( logger.all() ).to.deep.equal( {
+ error: [],
+ info: []
+ } );
+
+ logger.log( 'info', 'Info message.' );
+
+ expect( logger.all() ).to.deep.equal( {
+ error: [],
+ info: [ 'Info message.' ]
+ } );
+ } );
+
+ it( 'stores messages of the "error" type', () => {
+ expect( logger.all() ).to.deep.equal( {
+ error: [],
+ info: []
+ } );
+
+ logger.log( 'error', 'Error message.' );
+
+ expect( logger.all() ).to.deep.equal( {
+ error: [ 'Error message.' ],
+ info: []
+ } );
+ } );
+
+ it( 'trims whitespaces from received messages', () => {
+ expect( logger.all() ).to.deep.equal( {
+ error: [],
+ info: []
+ } );
+
+ logger.log( 'info', ' Info message.\n ' );
+
+ expect( logger.all() ).to.deep.equal( {
+ error: [],
+ info: [ 'Info message.' ]
+ } );
+ } );
+
+ it( 'ignores the "undefined" value passed as the message', () => {
+ expect( logger.all() ).to.deep.equal( {
+ error: [],
+ info: []
+ } );
+
+ logger.log( 'info', undefined );
+
+ expect( logger.all() ).to.deep.equal( {
+ error: [],
+ info: []
+ } );
+ } );
+
+ it( 'ignores messages consisting of whitespace alone', () => {
+ expect( logger.all() ).to.deep.equal( {
+ error: [],
+ info: []
+ } );
+
+ logger.log( 'info', ' ' );
+
+ expect( logger.all() ).to.deep.equal( {
+ error: [],
+ info: []
+ } );
+ } );
+ } );
+
+ describe( 'concat()', () => {
+ it( 'passes messages of the respective types to the info() and error() functions', () => {
+ logger.info = stubs.info;
+ logger.error = stubs.error;
+
+ logger.concat( {
+ info: [ 'Info message 1.', 'Info message 2.' ],
+ error: [ 'Error message 1.', 'Error message 2.' ]
+ } );
+
+ expect( stubs.info.callCount ).to.equal( 2 );
+ expect( stubs.info.getCall( 0 ).args[ 0 ] ).to.equal( 'Info message 1.' );
+ expect( stubs.info.getCall( 1 ).args[ 0 ] ).to.equal( 'Info message 2.' );
+
+ expect( stubs.error.callCount ).to.equal( 2 );
+ expect( stubs.error.getCall( 0 ).args[ 0 ] ).to.equal( 'Error message 1.' );
+ expect( stubs.error.getCall( 1 ).args[ 0 ] ).to.equal( 'Error message 2.' );
+ } );
+ } );
+
+ describe( 'all()', () => {
+ it( 'returns all stored messages', () => {
+ logger.concat( {
+ info: [ 'Info message 1.', 'Info message 2.' ],
+ error: [ 'Error message 1.', 'Error message 2.' ]
+ } );
+
+ expect( logger.all() ).to.deep.equal( {
+ info: [ 'Info message 1.', 'Info message 2.' ],
+ error: [ 'Error message 1.', 'Error message 2.' ]
+ } );
+ } );
+ } );
+ } );
+} );