From 2016201e8f9167cc991a1380011af7bd2adf0eab Mon Sep 17 00:00:00 2001 From: Pujal Date: Sun, 9 Feb 2025 12:35:04 -0500 Subject: [PATCH] truncate lrecl functionality Signed-off-by: Pujal --- .../methods/copy/Copy.system.test.ts | 35 +++++++++++++++++ .../__unit__/methods/copy/Copy.unit.test.ts | 39 ++++++++++++++++++- packages/zosfiles/src/methods/copy/Copy.ts | 22 +++++++++-- .../methods/copy/doc/ICopyDatasetOptions.ts | 2 +- 4 files changed, 92 insertions(+), 6 deletions(-) diff --git a/packages/zosfiles/__tests__/__system__/methods/copy/Copy.system.test.ts b/packages/zosfiles/__tests__/__system__/methods/copy/Copy.system.test.ts index 9b1bee5d2..dede24c5a 100644 --- a/packages/zosfiles/__tests__/__system__/methods/copy/Copy.system.test.ts +++ b/packages/zosfiles/__tests__/__system__/methods/copy/Copy.system.test.ts @@ -23,6 +23,7 @@ import { tmpdir } from "os"; import path = require("path"); import * as fs from "fs"; import { List } from "@zowe/zos-files-for-zowe-sdk"; +import * as util from "util"; let REAL_SESSION: Session; let REAL_TARGET_SESSION: Session; @@ -157,6 +158,40 @@ describe("Copy", () => { expect(contents2).toBeTruthy(); expect(contents1.toString()).toEqual(contents2.toString()); }); + it("Should handle truncation errors and log them to a file", async () => { + let error; + let response; + + const uploadFileToDatasetSpy = jest.spyOn(Upload, 'fileToDataset').mockImplementation(async (session, filePath, dsn) => { + if (filePath === fileLocation) { + throw new Error("Truncation of a record occurred during an I/O operation"); + } + return Promise.resolve() as any; + }); + const copyDataSetSpy = jest.spyOn(Copy, 'dataSet').mockImplementation(async () => { + return { + success: true, + commandResponse: ZosFilesMessages.datasetCopiedSuccessfully.message + " " + util.format(ZosFilesMessages.membersContentTruncated.message) + }; + }); + try { + response = await Copy.dataSet( + REAL_SESSION, + {dsn: toDataSetName}, + {"from-dataset": { + dsn:fromDataSetName + }} + ); + } catch (err) { + error = err; + Imperative.console.info(`Error: ${inspect(err)}`); + } + expect(response).toBeTruthy(); + expect(response.success).toBe(true); + expect(response.commandResponse).toContain(ZosFilesMessages.datasetCopiedSuccessfully.message + " " + util.format(ZosFilesMessages.membersContentTruncated.message)); + uploadFileToDatasetSpy.mockRestore(); + copyDataSetSpy.mockRestore(); + }); afterEach(() => { fs.rmSync(downloadDir, { recursive: true, force: true }); }); diff --git a/packages/zosfiles/__tests__/__unit__/methods/copy/Copy.unit.test.ts b/packages/zosfiles/__tests__/__unit__/methods/copy/Copy.unit.test.ts index ec1f5ecb5..1898d676c 100644 --- a/packages/zosfiles/__tests__/__unit__/methods/copy/Copy.unit.test.ts +++ b/packages/zosfiles/__tests__/__unit__/methods/copy/Copy.unit.test.ts @@ -16,6 +16,8 @@ import { error } from "console"; import * as util from "util"; import { Copy, Create, Get, List, Upload, ZosFilesConstants, ZosFilesMessages, IZosFilesResponse, Download, ZosFilesUtils } from "../../../../src"; import { ZosmfHeaders, ZosmfRestClient } from "@zowe/core-for-zowe-sdk"; +import path = require("path"); +import { tmpdir } from "os"; describe("Copy", () => { const dummySession = new Session({ @@ -770,6 +772,7 @@ describe("Copy", () => { const rmSync = jest.spyOn(fs, "rmSync"); const listDatasetSpy = jest.spyOn(List, "dataSet"); const hasIdenticalMemberNames = jest.spyOn(Copy as any, "hasIdenticalMemberNames"); + const writeFileSync = jest.spyOn(fs, "writeFileSync"); beforeEach(() => { hasIdenticalMemberNames.mockRestore(); @@ -901,6 +904,38 @@ describe("Copy", () => { commandResponse: ZosFilesMessages.datasetCopiedSuccessfully.message, }); }); + it("should handle truncation errors and log them to a file", async () => { + const truncatedMembersFilePath = path.join(tmpdir(), "errorMembers.txt"); + let caughtError; + let response; + const sourceResponse = { + apiResponse: { + items: [ + { member: "mem1" }, + { member: "mem2" }, + ] + } + }; + const fileList = ["mem1", "mem2"]; + listAllMembersSpy.mockImplementation(async (): Promise => sourceResponse); + downloadAllMembersSpy.mockImplementation(async (): Promise => undefined); + fileListPathSpy.mockReturnValue(fileList); + generateMemName.mockReturnValue("mem1"); + readStream.mockReturnValue("test" as any); + uploadSpy.mockImplementation((dummySession, readStream, dsn) => { + if (dsn.includes("mem1")) { + return Promise.reject(new Error("Truncation of a record occurred during an I/O operation")); + } + return Promise.resolve() as any; + }); + try{ + response = await Copy.copyPDS(dummySession, fromDataSetName, toDataSetName); + } + catch(e) { + caughtError = e; + } + expect(response.commandResponse).toContain("Data set copied successfully. Member(s)' contents were truncated due to insufficient record lines. You can view the list of members here: " + truncatedMembersFilePath); + }); describe("hasIdenticalMemberNames", () => { const listAllMembersSpy = jest.spyOn(List, "allMembers"); @@ -909,7 +944,7 @@ describe("Copy", () => { jest.clearAllMocks(); }); it("should return true if the source and target have identical member names", async () => { - listAllMembersSpy.mockImplementation(async (session, dsName): Promise => { + listAllMembersSpy.mockImplementation(async (_session, dsName): Promise => { if (dsName === fromDataSetName) { return { apiResponse: { @@ -949,7 +984,7 @@ describe("Copy", () => { ] } }; - listAllMembersSpy.mockImplementation(async (session, dsName): Promise => { + listAllMembersSpy.mockImplementation(async (_session, dsName): Promise => { if (dsName === fromDataSetName) { return sourceResponse; } else if (dsName === toDataSetName) { diff --git a/packages/zosfiles/src/methods/copy/Copy.ts b/packages/zosfiles/src/methods/copy/Copy.ts index 85ba90485..54518073a 100644 --- a/packages/zosfiles/src/methods/copy/Copy.ts +++ b/packages/zosfiles/src/methods/copy/Copy.ts @@ -210,7 +210,6 @@ export class Copy { * * @see https://www.ibm.com/support/knowledgecenter/en/SSLTBW_2.1.0/com.ibm.zos.v2r1.izua700/IZUHPINFO_API_PutDataSetMemberUtilities.htm */ - public static async copyPDS ( session: AbstractSession, fromPds: string, toPds: string): Promise { try { @@ -227,12 +226,29 @@ export class Copy { const downloadDir = path.join(tmpdir(), fromPds); await Download.allMembers(session, fromPds, {directory:downloadDir}); const uploadFileList: string[] = ZosFilesUtils.getFileListFromPath(downloadDir); + const truncatedMembers: string[] = []; for (const file of uploadFileList) { const memName = ZosFilesUtils.generateMemberName(file); const uploadingDsn = `${toPds}(${memName})`; - const uploadStream = IO.createReadStream(file); - await Upload.streamToDataSet(session, uploadStream, uploadingDsn); + try { + const uploadStream = IO.createReadStream(file); + await Upload.streamToDataSet(session, uploadStream, uploadingDsn); + } + catch(error) { + if(error.message && error.message.includes("Truncation of a record occurred during an I/O operation")) { + truncatedMembers.push(memName); + } + continue; + } + } + if(truncatedMembers.length > 0) { + const errorMembersFile = path.join(tmpdir(), 'errorMembers.txt'); + fs.writeFileSync(errorMembersFile, truncatedMembers.join('\n')); + return { + success: true, + commandResponse: ZosFilesMessages.datasetCopiedSuccessfully.message + " " + util.format(ZosFilesMessages.membersContentTruncated.message, errorMembersFile) + }; } fs.rmSync(downloadDir, {recursive: true}); return { diff --git a/packages/zosfiles/src/methods/copy/doc/ICopyDatasetOptions.ts b/packages/zosfiles/src/methods/copy/doc/ICopyDatasetOptions.ts index 948f69bfb..3554fb39c 100644 --- a/packages/zosfiles/src/methods/copy/doc/ICopyDatasetOptions.ts +++ b/packages/zosfiles/src/methods/copy/doc/ICopyDatasetOptions.ts @@ -50,7 +50,7 @@ export interface ICopyDatasetOptions extends IZosFilesOptions { /** * Prompt for duplicates - * @returns True if target data set members + * @returns `True` if the copy operation will overwrite an existing member; `false` otherwise */ promptForLikeNamedMembers?: () => Promise; }