Skip to content
This repository has been archived by the owner on Nov 6, 2020. It is now read-only.

Handle _JAVA_OPTIONS env var (used to address a Java heap error) #228

Merged
merged 3 commits into from
May 12, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions src/helpers/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,14 @@ import * as opener from "opener";

export class Utils {

public static FormatMessage(message: string): string {
if (message) {
//Replace newlines with spaces
return message.replace(/\r\n/g, " ").replace(/\n/g, " ").trim();
}
return message;
}

//gitDir provided for unit testing purposes
public static FindGitFolder(startingPath: string, gitDir?: string): string {
if (!fs.existsSync(startingPath)) { return undefined; }
Expand Down
12 changes: 2 additions & 10 deletions src/helpers/vscodeutils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,20 +48,12 @@ export class VsCodeUtils {
return value;
}

public static FormatMessage(message: string): string {
if (message) {
//Replace newlines with spaces
return message.replace(/\r\n/g, " ").replace(/\n/g, " ").trim();
}
return message;
}

//We have a single method to display either simple messages (with no options) or messages
//that have multiple buttons that can run commands, open URLs, send telemetry, etc.
public static async ShowErrorMessage(message: string, ...urlMessageItem: IButtonMessageItem[]): Promise<void> {
//The following "cast" allows us to pass our own type around (and not reference "vscode" via an import)
const messageItems: ButtonMessageItem[] = <ButtonMessageItem[]>urlMessageItem;
const messageToDisplay: string = `(${Constants.ExtensionName}) ${VsCodeUtils.FormatMessage(message)}`;
const messageToDisplay: string = `(${Constants.ExtensionName}) ${Utils.FormatMessage(message)}`;

//Use the typescript spread operator to pass the rest parameter to showErrorMessage
const chosenItem: IButtonMessageItem = await window.showErrorMessage(messageToDisplay, ...messageItems);
Expand All @@ -80,6 +72,6 @@ export class VsCodeUtils {
}

public static ShowWarningMessage(message: string) {
window.showWarningMessage("(" + Constants.ExtensionName + ") " + VsCodeUtils.FormatMessage(message));
window.showWarningMessage("(" + Constants.ExtensionName + ") " + Utils.FormatMessage(message));
}
}
12 changes: 10 additions & 2 deletions src/tfvc/commands/commandhelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { parseString } from "xml2js";
import { Constants } from "../../helpers/constants";
import { Logger } from "../../helpers/logger";
import { Strings } from "../../helpers/strings";
import { Utils } from "../../helpers/utils";
import { IButtonMessageItem } from "../../helpers/vscodeutils.interfaces";
import { TfvcError, TfvcErrorCodes } from "../tfvcerror";
import { IExecutionResult } from "../interfaces";
Expand Down Expand Up @@ -44,7 +45,7 @@ export class CommandHelper {
if (result.exitCode) {
let tfvcErrorCode: string = TfvcErrorCodes.UnknownError;
let message: string;
let messageOptions: IButtonMessageItem[];
let messageOptions: IButtonMessageItem[] = [];

if (/Authentication failed/.test(result.stderr)) {
tfvcErrorCode = TfvcErrorCodes.AuthenticationFailed;
Expand All @@ -64,6 +65,11 @@ export class CommandHelper {
} else if (/'java' is not recognized as an internal or external command/i.test(result.stderr)) {
tfvcErrorCode = TfvcErrorCodes.NotFound;
message = Strings.TfInitializeFailureError;
} else if (/Error occurred during initialization of VM/i.test(result.stdout)) {
//Example: "Error occurred during initialization of VM\nCould not reserve enough space for 2097152KB object heap\n"
//This one occurs with the error message in stdout!
tfvcErrorCode = TfvcErrorCodes.NotFound;
message = `${Strings.TfInitializeFailureError} (${Utils.FormatMessage(result.stdout)})`;
} else if (/There is no working folder mapping/i.test(result.stderr)) {
tfvcErrorCode = TfvcErrorCodes.FileNotInMappings;
} else if (/could not be found in your workspace, or you do not have permission to access it./i.test(result.stderr)) {
Expand All @@ -78,7 +84,9 @@ export class CommandHelper {
message = result.stderr ? result.stderr : result.stdout;
}

Logger.LogDebug(`TFVC errors: ${result.stderr}`);
//We're banking on content being in one or the other with preference to stderr
const logMsg: string = result.stderr ? result.stderr : result.stdout;
Logger.LogDebug(`TFVC errors: ${logMsg}`);

throw new TfvcError({
message: message || Strings.TfExecFailedError,
Expand Down
15 changes: 14 additions & 1 deletion src/tfvc/commands/findconflicts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,22 @@ export class FindConflicts implements ITfvcCommand<IConflict[]> {
}

const conflicts: IConflict[] = [];
const lines: string[] = CommandHelper.SplitIntoLines(executionResult.stderr, false, true);
//"Picked up _JAVA_OPTIONS: -Xmx1024M"
let outputToProcess: string = executionResult.stderr;
if (outputToProcess && outputToProcess.includes("_JAVA_OPTIONS")) {
//When you don't need _JAVA_OPTIONS set, the results we want are always in stderr (this is the default case)
//With _JAVA_OPTIONS set and there are no conflicts, _JAVA_OPTIONS is in stderr but the result we want to process is moved to stdout
//With _JAVA_OPTIONS set and there are conflicts, _JAVA_OPTIONS will appear in stderr along with the results also in stderr (and stdout will be empty)
if (executionResult.stdout && executionResult.stdout.length > 0) {
outputToProcess = executionResult.stdout;
}
}
const lines: string[] = CommandHelper.SplitIntoLines(outputToProcess, false, true);
for (let i: number = 0; i < lines.length; i++) {
const line: string = lines[i];
if (line.includes("_JAVA_OPTIONS")) {
continue; //This is not a conflict
}
const colonIndex: number = line.lastIndexOf(":");
if (colonIndex >= 0) {
const localPath: string = line.slice(0, colonIndex);
Expand Down
4 changes: 2 additions & 2 deletions src/tfvc/tfvc-extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -342,10 +342,10 @@ export class TfvcExtension {
try {
await funcToTry(prefix);
} catch (err) {
TfvcOutput.AppendLine(VsCodeUtils.FormatMessage(`[${prefix}] ${err.message}`));
TfvcOutput.AppendLine(Utils.FormatMessage(`[${prefix}] ${err.message}`));
//If we also have text in err.stdout, provide that to the output channel
if (err.stdout) { //TODO: perhaps just for 'Checkin'? Or the CLC?
TfvcOutput.AppendLine(VsCodeUtils.FormatMessage(`[${prefix}] ${err.stdout}`));
TfvcOutput.AppendLine(Utils.FormatMessage(`[${prefix}] ${err.stdout}`));
}
const messageItem: IButtonMessageItem = { title : Strings.ShowTfvcOutput, command: TfvcCommandNames.ShowOutput };
VsCodeUtils.ShowErrorMessage(err.message, messageItem);
Expand Down
55 changes: 55 additions & 0 deletions test/tfvc/commands/findconflicts.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,61 @@ describe("Tfvc-FindConflictsCommand", function() {
assert.equal(results[8].type, ConflictType.DELETE_TARGET);
});

//With _JAVA_OPTIONS set and there are conflicts, _JAVA_OPTIONS will appear in stderr along with the results also in stderr (and stdout will be empty)
it("should verify parse output - one of each type - _JAVA_OPTIONS", async function() {
const localPath: string = "/usr/alias/repo1";
const cmd: FindConflicts = new FindConflicts(undefined, localPath);
const executionResult: IExecutionResult = {
exitCode: 0,
stdout: "",
stderr: "contentChange.txt: The item content has changed\n" +
"addConflict.txt: Another item with the same name exists on the server\n" +
"nameChange.txt: The item name has changed\n" +
"nameAndContentChange.txt: The item name and content have changed\n" +
"anotherNameAndContentChange.txt: You have a conflicting pending change\n" +
"contentChange2.txt: The item content has changed\n" +
"deleted.txt: The item has already been deleted\n" +
"branchEdit.txt: The source and target both have changes\n" +
"branchDelete.txt: The item has been deleted in the target branch\n" +
"Picked up _JAVA_OPTIONS: -Xmx1024M"
};

const results: IConflict[] = await cmd.ParseOutput(executionResult);
assert.equal(results.length, 9);
assert.equal(results[0].localPath, "contentChange.txt");
assert.equal(results[0].type, ConflictType.CONTENT);
assert.equal(results[1].localPath, "addConflict.txt");
assert.equal(results[1].type, ConflictType.CONTENT);
assert.equal(results[2].localPath, "nameChange.txt");
assert.equal(results[2].type, ConflictType.RENAME);
assert.equal(results[3].localPath, "nameAndContentChange.txt");
assert.equal(results[3].type, ConflictType.NAME_AND_CONTENT);
assert.equal(results[4].localPath, "anotherNameAndContentChange.txt");
assert.equal(results[4].type, ConflictType.NAME_AND_CONTENT);
assert.equal(results[5].localPath, "contentChange2.txt");
assert.equal(results[5].type, ConflictType.CONTENT);
assert.equal(results[6].localPath, "deleted.txt");
assert.equal(results[6].type, ConflictType.DELETE);
assert.equal(results[7].localPath, "branchEdit.txt");
assert.equal(results[7].type, ConflictType.MERGE);
assert.equal(results[8].localPath, "branchDelete.txt");
assert.equal(results[8].type, ConflictType.DELETE_TARGET);
});

//With _JAVA_OPTIONS set and there are no conflicts, _JAVA_OPTIONS is in stderr but the result we want to process is moved to stdout
it("should verify parse output - no conflicts - _JAVA_OPTIONS", async function() {
const localPath: string = "/usr/alias/repo1";
const cmd: FindConflicts = new FindConflicts(undefined, localPath);
const executionResult: IExecutionResult = {
exitCode: 0,
stdout: "There are no conflicts to resolve.\n",
stderr: "Picked up _JAVA_OPTIONS: -Xmx1024M\n"
};

const results: IConflict[] = await cmd.ParseOutput(executionResult);
assert.equal(results.length, 0);
});

it("should verify parse output - errors - exit code 100", async function() {
const localPath: string = "/usr/alias/repo 1";
const cmd: FindConflicts = new FindConflicts(undefined, localPath);
Expand Down
21 changes: 21 additions & 0 deletions test/tfvc/commands/getversion.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,27 @@ describe("Tfvc-GetVersionCommand", function() {
}
});

it("should verify parse output - java object heap error", async function() {
const cmd: GetVersion = new GetVersion();
const executionResult: IExecutionResult = {
exitCode: 1,
stdout: "Error occurred during initialization of VM\r\nCould not reserve enough space for 2097152KB object heap\r\n",
stderr: undefined
};

let threw: boolean = false;

try {
await cmd.ParseOutput(executionResult);
} catch (err) {
assert.equal(err.tfvcErrorCode, TfvcErrorCodes.NotFound);
assert.isTrue(err.message.startsWith(Strings.TfInitializeFailureError));
threw = true;
} finally {
assert.isTrue(threw, "Checking for Java object heap error did not throw an error.");
}
});

it("should verify parse Exe output - error exit code", async function() {
const cmd: GetVersion = new GetVersion();
const executionResult: IExecutionResult = {
Expand Down