From 75c93bf758d1e8b65a4cd5f08743b387c308b1cd Mon Sep 17 00:00:00 2001 From: Sofia Leon Date: Thu, 4 Jun 2026 23:03:45 +0000 Subject: [PATCH 01/14] feat(internal/librarian/java): support alternate license header files --- internal/config/language.go | 3 + internal/librarian/java/postprocess.go | 32 ++++++--- internal/librarian/java/postprocess_test.go | 73 +++++++++++++++++---- 3 files changed, 86 insertions(+), 22 deletions(-) diff --git a/internal/config/language.go b/internal/config/language.go index 5900d13c7a1..c900ff5e4d9 100644 --- a/internal/config/language.go +++ b/internal/config/language.go @@ -476,6 +476,9 @@ type DartPackage struct { // TODO(https://github.com/googleapis/librarian/issues/4130): // add fill defaults for fields with default. type JavaModule struct { + // AlternateHeaders is the path to a file containing alternate license header text. + AlternateHeaders string `yaml:"alternate_headers,omitempty"` + // APIIDOverride is the ID of the API (e.g., "pubsub.googleapis.com"), // allows the "api_id" field in .repo-metadata.json to be overridden. // Defaults to "{library.api_shortname}.googleapis.com". diff --git a/internal/librarian/java/postprocess.go b/internal/librarian/java/postprocess.go index 94cb101a0ea..ac2e835a78f 100644 --- a/internal/librarian/java/postprocess.go +++ b/internal/librarian/java/postprocess.go @@ -113,7 +113,7 @@ func postProcessAPI(ctx context.Context, p postProcessParams) error { return fmt.Errorf("failed to unzip %s: %w", srcjarPath, err) } } - if err := addHeadersIfRequired(p, []string{gRPCDir, protoDir}); err != nil { + if err := addHeaders(p, []string{gRPCDir, protoDir}); err != nil { return err } if err := copyFiles(p); err != nil { @@ -146,12 +146,9 @@ func postProcessAPI(ctx context.Context, p postProcessParams) error { return nil } -func addHeadersIfRequired(p postProcessParams, dirs []string) error { - if p.javaAPI.Monolithic { - return nil - } +func addHeaders(p postProcessParams, dirs []string) error { for _, dir := range dirs { - if err := addMissingHeaders(dir); err != nil { + if err := addMissingHeaders(p, dir); err != nil { return fmt.Errorf("failed to fix headers in %s: %w", dir, err) } } @@ -160,9 +157,11 @@ func addHeadersIfRequired(p postProcessParams, dirs []string) error { // addMissingHeaders prepends the license header to all Java files in the given directory // if they don't already have one. -func addMissingHeaders(dir string) error { - year := time.Now().Year() - licenseText := buildLicenseText(year) +func addMissingHeaders(p postProcessParams, dir string) error { + headerText, err := getLicenseText(p) + if err != nil { + return err + } return filepath.WalkDir(dir, func(path string, d os.DirEntry, err error) error { if err != nil || !d.Type().IsRegular() || filepath.Ext(path) != ".java" { return err @@ -174,10 +173,23 @@ func addMissingHeaders(dir string) error { if license.HasHeader(content) { return nil } - return os.WriteFile(path, append([]byte(licenseText), content...), 0644) + return os.WriteFile(path, append([]byte(headerText), content...), 0644) }) } +func getLicenseText(p postProcessParams) (string, error) { + if p.library != nil && p.library.Java != nil && p.library.Java.AlternateHeaders != "" { + headerPath := filepath.Join(p.outDir, p.library.Java.AlternateHeaders) + b, err := os.ReadFile(headerPath) + if err != nil { + return "", err + } + return string(b), nil + } + year := time.Now().Year() + return buildLicenseText(year), nil +} + func copyFiles(p postProcessParams) error { if p.javaAPI == nil || len(p.javaAPI.CopyFiles) == 0 { return nil diff --git a/internal/librarian/java/postprocess_test.go b/internal/librarian/java/postprocess_test.go index 72ea58cf9ee..51dffc4af23 100644 --- a/internal/librarian/java/postprocess_test.go +++ b/internal/librarian/java/postprocess_test.go @@ -427,15 +427,15 @@ func TestRestructureModules_Monolithic(t *testing.T) { } } -func TestPostProcessAPI_SkipHeaders(t *testing.T) { +func TestPostProcessAPI_AddHeaders(t *testing.T) { t.Parallel() for _, test := range []struct { name string - monolithic bool - wantHeader bool + altHeader string + wantHeader string }{ - {"default - adds header", false, true}, - {"monolithic - skips header", true, false}, + {"default", "", "Copyright"}, + {"alternate", "/* Alternate Copyright */\n", "Alternate"}, } { t.Run(test.name, func(t *testing.T) { outdir := t.TempDir() @@ -453,10 +453,17 @@ func TestPostProcessAPI_SkipHeaders(t *testing.T) { outDir: outdir, apiBase: apiBase, library: &config.Library{Java: &config.JavaModule{}}, - javaAPI: &config.JavaAPI{Monolithic: test.monolithic}, + javaAPI: &config.JavaAPI{}, } - // We only care about the skip logic before restructure/cleanup. - if err := addHeadersIfRequired(p, []string{gRPCDir}); err != nil { + if test.altHeader != "" { + headerFile := filepath.Join(outdir, "header.txt") + if err := os.WriteFile(headerFile, []byte(test.altHeader), 0644); err != nil { + t.Fatal(err) + } + p.library.Java.AlternateHeaders = "header.txt" + } + + if err := addHeaders(p, []string{gRPCDir}); err != nil { t.Fatal(err) } @@ -464,9 +471,8 @@ func TestPostProcessAPI_SkipHeaders(t *testing.T) { if err != nil { t.Fatal(err) } - hasHeader := bytes.Contains(got, []byte("Copyright")) - if hasHeader != test.wantHeader { - t.Errorf("hasHeader = %v, want %v", hasHeader, test.wantHeader) + if !bytes.Contains(got, []byte(test.wantHeader)) { + t.Errorf("got = %s, want to contain %s", got, test.wantHeader) } }) } @@ -790,6 +796,7 @@ func TestRunOwlBot_Error(t *testing.T) { func TestAddMissingHeaders(t *testing.T) { for _, test := range []struct { name string + p postProcessParams filename string content string wantModified bool @@ -816,6 +823,19 @@ func TestAddMissingHeaders(t *testing.T) { filename: "test.txt", content: "some text", }, + { + name: "alternate header", + p: postProcessParams{ + library: &config.Library{ + Java: &config.JavaModule{ + AlternateHeaders: "header.txt", + }, + }, + }, + filename: "AlternateHeader.java", + content: "package com.example;", + wantModified: true, + }, } { t.Run(test.name, func(t *testing.T) { t.Parallel() @@ -825,7 +845,20 @@ func TestAddMissingHeaders(t *testing.T) { if err := os.WriteFile(path, originalContent, 0644); err != nil { t.Fatal(err) } - if err := addMissingHeaders(tmpDir); err != nil { + + p := test.p + p.outDir = tmpDir + if p.library != nil && p.library.Java != nil && p.library.Java.AlternateHeaders != "" { + headerPath := filepath.Join(tmpDir, p.library.Java.AlternateHeaders) + if err := os.MkdirAll(filepath.Dir(headerPath), 0755); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(headerPath, []byte("/* Alternate Header */\n"), 0644); err != nil { + t.Fatal(err) + } + } + + if err := addMissingHeaders(p, tmpDir); err != nil { t.Fatal(err) } @@ -841,6 +874,22 @@ func TestAddMissingHeaders(t *testing.T) { } } +func TestAddMissingHeaders_AlternateHeadersError(t *testing.T) { + t.Parallel() + tmpDir := t.TempDir() + p := postProcessParams{ + outDir: tmpDir, + library: &config.Library{ + Java: &config.JavaModule{ + AlternateHeaders: "missing-header.txt", + }, + }, + } + if err := addMissingHeaders(p, tmpDir); err == nil { + t.Error("expected error for missing alternate headers file, got nil") + } +} + func TestCopyFiles(t *testing.T) { t.Parallel() outdir := t.TempDir() From c9c166d4eb976c5d883ce898305fb4847173a4f3 Mon Sep 17 00:00:00 2001 From: Sofia Leon Date: Thu, 4 Jun 2026 23:20:00 +0000 Subject: [PATCH 02/14] add back in monolithic block for backwards compatibility --- internal/librarian/java/postprocess.go | 3 +++ internal/librarian/java/postprocess_test.go | 10 ++++++---- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/internal/librarian/java/postprocess.go b/internal/librarian/java/postprocess.go index ac2e835a78f..5c5e1980c2e 100644 --- a/internal/librarian/java/postprocess.go +++ b/internal/librarian/java/postprocess.go @@ -147,6 +147,9 @@ func postProcessAPI(ctx context.Context, p postProcessParams) error { } func addHeaders(p postProcessParams, dirs []string) error { + if p.javaAPI.Monolithic { + return nil + } for _, dir := range dirs { if err := addMissingHeaders(p, dir); err != nil { return fmt.Errorf("failed to fix headers in %s: %w", dir, err) diff --git a/internal/librarian/java/postprocess_test.go b/internal/librarian/java/postprocess_test.go index 51dffc4af23..9c78a832999 100644 --- a/internal/librarian/java/postprocess_test.go +++ b/internal/librarian/java/postprocess_test.go @@ -427,15 +427,17 @@ func TestRestructureModules_Monolithic(t *testing.T) { } } -func TestPostProcessAPI_AddHeaders(t *testing.T) { +func TestPostProcessAPI_SkipHeaders(t *testing.T) { t.Parallel() for _, test := range []struct { name string + monolithic bool altHeader string wantHeader string }{ - {"default", "", "Copyright"}, - {"alternate", "/* Alternate Copyright */\n", "Alternate"}, + {"default adds header", false, "", "Copyright"}, + {"monolithic skips header", true, "", "package"}, + {"alternate adds alt header", false, "/* Alternate */\n", "Alternate"}, } { t.Run(test.name, func(t *testing.T) { outdir := t.TempDir() @@ -453,7 +455,7 @@ func TestPostProcessAPI_AddHeaders(t *testing.T) { outDir: outdir, apiBase: apiBase, library: &config.Library{Java: &config.JavaModule{}}, - javaAPI: &config.JavaAPI{}, + javaAPI: &config.JavaAPI{Monolithic: test.monolithic}, } if test.altHeader != "" { headerFile := filepath.Join(outdir, "header.txt") From a28e36c9f0e4597fd44594eadd74bf84d33a5425 Mon Sep 17 00:00:00 2001 From: sofisl <55454395+sofisl@users.noreply.github.com> Date: Thu, 4 Jun 2026 16:36:31 -0700 Subject: [PATCH 03/14] Update internal/librarian/java/postprocess_test.go Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: sofisl <55454395+sofisl@users.noreply.github.com> --- internal/librarian/java/postprocess_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/librarian/java/postprocess_test.go b/internal/librarian/java/postprocess_test.go index 9c78a832999..13c0708e753 100644 --- a/internal/librarian/java/postprocess_test.go +++ b/internal/librarian/java/postprocess_test.go @@ -435,9 +435,9 @@ func TestPostProcessAPI_SkipHeaders(t *testing.T) { altHeader string wantHeader string }{ - {"default adds header", false, "", "Copyright"}, + {"default adds header", false, "", "/*\n * Copyright"}, {"monolithic skips header", true, "", "package"}, - {"alternate adds alt header", false, "/* Alternate */\n", "Alternate"}, + {"alternate adds alt header", false, "/* Alternate */\n", "/* Alternate */"}, } { t.Run(test.name, func(t *testing.T) { outdir := t.TempDir() From 504d04171627cea73b00922900c0aa363725c369 Mon Sep 17 00:00:00 2001 From: sofisl <55454395+sofisl@users.noreply.github.com> Date: Thu, 4 Jun 2026 16:36:45 -0700 Subject: [PATCH 04/14] Update internal/librarian/java/postprocess_test.go Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: sofisl <55454395+sofisl@users.noreply.github.com> --- internal/librarian/java/postprocess_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/librarian/java/postprocess_test.go b/internal/librarian/java/postprocess_test.go index 13c0708e753..97b1768a103 100644 --- a/internal/librarian/java/postprocess_test.go +++ b/internal/librarian/java/postprocess_test.go @@ -473,8 +473,8 @@ func TestPostProcessAPI_SkipHeaders(t *testing.T) { if err != nil { t.Fatal(err) } - if !bytes.Contains(got, []byte(test.wantHeader)) { - t.Errorf("got = %s, want to contain %s", got, test.wantHeader) + if !bytes.HasPrefix(got, []byte(test.wantHeader)) { + t.Errorf("mismatch got = %q, want %q", got, test.wantHeader) } }) } From cf3f6aa7147b9760e67f3b2e1ee06db4e8180c78 Mon Sep 17 00:00:00 2001 From: sofisl <55454395+sofisl@users.noreply.github.com> Date: Thu, 4 Jun 2026 16:36:57 -0700 Subject: [PATCH 05/14] Update internal/librarian/java/postprocess.go Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: sofisl <55454395+sofisl@users.noreply.github.com> --- internal/librarian/java/postprocess.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/librarian/java/postprocess.go b/internal/librarian/java/postprocess.go index 5c5e1980c2e..096a1d2a9c7 100644 --- a/internal/librarian/java/postprocess.go +++ b/internal/librarian/java/postprocess.go @@ -176,7 +176,7 @@ func addMissingHeaders(p postProcessParams, dir string) error { if license.HasHeader(content) { return nil } - return os.WriteFile(path, append([]byte(headerText), content...), 0644) + return os.WriteFile(path, append(headerText, content...), 0644) }) } From 0ef59280ffa9f3e4089415c9085e29a4a5d80a74 Mon Sep 17 00:00:00 2001 From: Sofia Leon Date: Thu, 4 Jun 2026 23:39:13 +0000 Subject: [PATCH 06/14] chore: fix problem --- internal/librarian/java/postprocess.go | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/internal/librarian/java/postprocess.go b/internal/librarian/java/postprocess.go index 096a1d2a9c7..def239b5049 100644 --- a/internal/librarian/java/postprocess.go +++ b/internal/librarian/java/postprocess.go @@ -180,17 +180,20 @@ func addMissingHeaders(p postProcessParams, dir string) error { }) } -func getLicenseText(p postProcessParams) (string, error) { - if p.library != nil && p.library.Java != nil && p.library.Java.AlternateHeaders != "" { +func getLicenseText(p postProcessParams) ([]byte, error) { + if p.library != nil && p.library.Java.AlternateHeaders != "" { headerPath := filepath.Join(p.outDir, p.library.Java.AlternateHeaders) b, err := os.ReadFile(headerPath) if err != nil { - return "", err + return nil, fmt.Errorf("failed to read alternate header file %s: %w", headerPath, err) } - return string(b), nil + if len(b) > 0 && b[len(b)-1] != '\n' { + b = append(b, '\n') + } + return b, nil } year := time.Now().Year() - return buildLicenseText(year), nil + return []byte(buildLicenseText(year)), nil } func copyFiles(p postProcessParams) error { From f6c2e1eb78bc41dea18e6e75780edbe04ac1bd88 Mon Sep 17 00:00:00 2001 From: Sofia Leon Date: Thu, 4 Jun 2026 23:45:01 +0000 Subject: [PATCH 07/14] fix config --- doc/config-schema.md | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/config-schema.md b/doc/config-schema.md index 521d79aa678..b99aa249933 100644 --- a/doc/config-schema.md +++ b/doc/config-schema.md @@ -315,6 +315,7 @@ This document describes the schema for the librarian.yaml. | Field | Type | Description | | :--- | :--- | :--- | +| `alternate_headers` | string | Is the path to a file containing alternate license header text. | | `api_id_override` | string | Is the ID of the API (e.g., "pubsub.googleapis.com"), allows the "api_id" field in .repo-metadata.json to be overridden. Defaults to "{library.api_shortname}.googleapis.com". | | `api_reference` | string | Is the URL for the API reference documentation. | | `api_description_override` | string | Allows the "api_description" field in .repo-metadata.json to be overridden. | From a10b4350dd1d2ad0c54c8a8a281c6f367289254b Mon Sep 17 00:00:00 2001 From: sofisl <55454395+sofisl@users.noreply.github.com> Date: Fri, 5 Jun 2026 10:33:52 -0700 Subject: [PATCH 08/14] Update internal/librarian/java/postprocess.go Co-authored-by: Joe Wang <106995533+JoeWang1127@users.noreply.github.com> Signed-off-by: sofisl <55454395+sofisl@users.noreply.github.com> --- internal/librarian/java/postprocess.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/librarian/java/postprocess.go b/internal/librarian/java/postprocess.go index def239b5049..22748003c19 100644 --- a/internal/librarian/java/postprocess.go +++ b/internal/librarian/java/postprocess.go @@ -160,7 +160,7 @@ func addHeaders(p postProcessParams, dirs []string) error { // addMissingHeaders prepends the license header to all Java files in the given directory // if they don't already have one. -func addMissingHeaders(p postProcessParams, dir string) error { +func addMissingHeaders(params postProcessParams, dir string) error { headerText, err := getLicenseText(p) if err != nil { return err From 229eeb03254fe055588d144a3a7aaab72ee97afa Mon Sep 17 00:00:00 2001 From: Sofia Leon Date: Fri, 5 Jun 2026 21:17:16 +0000 Subject: [PATCH 09/14] merge conflict --- internal/librarian/java/postprocess.go | 123 +++++++++++--------- internal/librarian/java/postprocess_test.go | 72 ++++++------ 2 files changed, 104 insertions(+), 91 deletions(-) diff --git a/internal/librarian/java/postprocess.go b/internal/librarian/java/postprocess.go index 22748003c19..abbb53e66d4 100644 --- a/internal/librarian/java/postprocess.go +++ b/internal/librarian/java/postprocess.go @@ -67,45 +67,51 @@ type libraryPostProcessParams struct { transports map[string]serviceconfig.Transport } -func postProcessLibrary(ctx context.Context, p libraryPostProcessParams) error { - if err := createOrVerifyOwlbotPy(p.outDir); err != nil { +func postProcessLibrary(ctx context.Context, params libraryPostProcessParams) error { + if err := createOrVerifyOwlbotPy(params.outDir); err != nil { return err } - bomVersion, err := findBOMVersion(p.cfg) + bomVersion, err := findBOMVersion(params.cfg) if err != nil { return err } - if err := removeKeptFilesFromStaging(p.library, p.outDir); err != nil { + if err := removeKeptFilesFromStaging(params.library, params.outDir); err != nil { return fmt.Errorf("failed to remove kept files from staging: %w", err) } - if err := runOwlBot(ctx, p.library, p.outDir, bomVersion); err != nil { + if err := runOwlBot(ctx, params.library, params.outDir, bomVersion); err != nil { return fmt.Errorf("%w: %w", errRunOwlBot, err) } - monorepoVersion, err := findMonorepoVersion(p.cfg) + monorepoVersion, err := findMonorepoVersion(params.cfg) if err != nil { return err } - if err := syncPOMs(p.library, p.outDir, monorepoVersion, p.metadata, p.transports); err != nil { + if err := syncPOMs(params.library, params.outDir, monorepoVersion, params.metadata, params.transports); err != nil { return fmt.Errorf("%w: %w", errSyncPOMs, err) } return nil } -func (p postProcessParams) gapicDir() string { return filepath.Join(p.outDir, p.apiBase, "gapic") } -func (p postProcessParams) gRPCDir() string { return filepath.Join(p.outDir, p.apiBase, "grpc") } -func (p postProcessParams) protoDir() string { return filepath.Join(p.outDir, p.apiBase, "proto") } -func (p postProcessParams) coords() APICoordinate { - return DeriveAPICoordinates(DeriveLibraryCoordinates(p.library), p.apiBase, p.javaAPI) +func (params postProcessParams) gapicDir() string { + return filepath.Join(params.outDir, params.apiBase, "gapic") +} +func (params postProcessParams) gRPCDir() string { + return filepath.Join(params.outDir, params.apiBase, "grpc") +} +func (params postProcessParams) protoDir() string { + return filepath.Join(params.outDir, params.apiBase, "proto") +} +func (params postProcessParams) coords() APICoordinate { + return DeriveAPICoordinates(DeriveLibraryCoordinates(params.library), params.apiBase, params.javaAPI) } func stagingDir(outDir string) string { return filepath.Join(outDir, owlbotStagingDir) } -func postProcessAPI(ctx context.Context, p postProcessParams) error { - gapicDir := p.gapicDir() - gRPCDir := p.gRPCDir() - protoDir := p.protoDir() +func postProcessAPI(ctx context.Context, params postProcessParams) error { + gapicDir := params.gapicDir() + gRPCDir := params.gRPCDir() + protoDir := params.protoDir() // Unzip the temp-codegen.srcjar into temporary {gapicDir} directory. srcjarPath := filepath.Join(gapicDir, "temp-codegen.srcjar") if _, err := os.Stat(srcjarPath); err == nil { @@ -113,45 +119,45 @@ func postProcessAPI(ctx context.Context, p postProcessParams) error { return fmt.Errorf("failed to unzip %s: %w", srcjarPath, err) } } - if err := addHeaders(p, []string{gRPCDir, protoDir}); err != nil { + if err := addHeaders(params, []string{gRPCDir, protoDir}); err != nil { return err } - if err := copyFiles(p); err != nil { + if err := copyFiles(params); err != nil { return fmt.Errorf("failed to copy files: %w", err) } - if err := restructureToStaging(p); err != nil { + if err := restructureToStaging(params); err != nil { return fmt.Errorf("failed to restructure to staging: %w", err) } // Generate clirr-ignored-differences.xml for the proto module. // We target the staging directory because runOwlBot hasn't moved the files // to their final destination yet. - coords := p.coords() - protoModuleRepoRoot := filepath.Join(p.outDir, coords.Proto.ArtifactID) - shouldGenerate, err := clirrIgnoreShouldGenerate(coords.Proto.ArtifactID, protoModuleRepoRoot, p.javaAPI.Monolithic) + coords := params.coords() + protoModuleRepoRoot := filepath.Join(params.outDir, coords.Proto.ArtifactID) + shouldGenerate, err := clirrIgnoreShouldGenerate(coords.Proto.ArtifactID, protoModuleRepoRoot, params.javaAPI.Monolithic) if err != nil { return fmt.Errorf("failed to check for clirr ignore file: %w", err) } if shouldGenerate { - protoModuleStagingRoot := filepath.Join(stagingDir(p.outDir), p.apiBase, coords.Proto.ArtifactID) + protoModuleStagingRoot := filepath.Join(stagingDir(params.outDir), params.apiBase, coords.Proto.ArtifactID) if err := generateClirrIgnore(protoModuleStagingRoot); err != nil { return fmt.Errorf("failed to generate clirr ignore file: %w", err) } } // Cleanup intermediate protoc output directory after restructuring - if err := os.RemoveAll(filepath.Join(p.outDir, p.apiBase)); err != nil { + if err := os.RemoveAll(filepath.Join(params.outDir, params.apiBase)); err != nil { return fmt.Errorf("failed to cleanup intermediate files: %w", err) } return nil } -func addHeaders(p postProcessParams, dirs []string) error { - if p.javaAPI.Monolithic { +func addHeaders(params postProcessParams, dirs []string) error { + if params.javaAPI.Monolithic { return nil } for _, dir := range dirs { - if err := addMissingHeaders(p, dir); err != nil { + if err := addMissingHeaders(params, dir); err != nil { return fmt.Errorf("failed to fix headers in %s: %w", dir, err) } } @@ -161,7 +167,7 @@ func addHeaders(p postProcessParams, dirs []string) error { // addMissingHeaders prepends the license header to all Java files in the given directory // if they don't already have one. func addMissingHeaders(params postProcessParams, dir string) error { - headerText, err := getLicenseText(p) + headerText, err := getLicenseText(params) if err != nil { return err } @@ -180,9 +186,12 @@ func addMissingHeaders(params postProcessParams, dir string) error { }) } -func getLicenseText(p postProcessParams) ([]byte, error) { - if p.library != nil && p.library.Java.AlternateHeaders != "" { - headerPath := filepath.Join(p.outDir, p.library.Java.AlternateHeaders) +// If a library has an alternate header file, this function will read the contents of +// the alternate_header property (a filepath). Otherwise it will grab the default license +// header. +func getLicenseText(params postProcessParams) ([]byte, error) { + if params.library != nil && params.library.Java != nil && params.library.Java.AlternateHeaders != "" { + headerPath := filepath.Join(params.outDir, params.library.Java.AlternateHeaders) b, err := os.ReadFile(headerPath) if err != nil { return nil, fmt.Errorf("failed to read alternate header file %s: %w", headerPath, err) @@ -196,12 +205,12 @@ func getLicenseText(p postProcessParams) ([]byte, error) { return []byte(buildLicenseText(year)), nil } -func copyFiles(p postProcessParams) error { - if p.javaAPI == nil || len(p.javaAPI.CopyFiles) == 0 { +func copyFiles(params postProcessParams) error { + if params.javaAPI == nil || len(params.javaAPI.CopyFiles) == 0 { return nil } - gapicDir := p.gapicDir() - for _, c := range p.javaAPI.CopyFiles { + gapicDir := params.gapicDir() + for _, c := range params.javaAPI.CopyFiles { src := filepath.Join(gapicDir, c.Source) dest := filepath.Join(gapicDir, c.Destination) if _, err := os.Stat(src); err != nil { @@ -248,16 +257,16 @@ func removeConflictingFiles(protoSrcDir string) error { // that matches the structure expected by owlbot.py. It nests modules under the // {apiBase} directory (e.g., owl-bot-staging/v1/proto-google-cloud-chat-v1) to // ensure synthtool preserves the module structure. -func restructureToStaging(p postProcessParams) error { - stagingDir := stagingDir(p.outDir) - destRoot := filepath.Join(stagingDir, p.apiBase) - if p.javaAPI.Monolithic { +func restructureToStaging(params postProcessParams) error { + stagingDir := stagingDir(params.outDir) + destRoot := filepath.Join(stagingDir, params.apiBase) + if params.javaAPI.Monolithic { destRoot = filepath.Join(destRoot, "src") } if err := os.MkdirAll(destRoot, 0755); err != nil { return fmt.Errorf("failed to create staging directory: %w", err) } - return restructureModules(p, destRoot) + return restructureModules(params, destRoot) } type moveAction struct { @@ -282,10 +291,10 @@ func restructure(actions []moveAction) error { // restructureModules moves the generated code from the temporary versioned directory // tree into the destination root directory for GAPIC, Proto, gRPC, and samples. // It also copies the relevant proto files into the proto module. -func restructureModules(p postProcessParams, destRoot string) error { - coords := p.coords() - tempProtoSrcDir := p.protoDir() - if p.library.Name != commonProtosLibrary { +func restructureModules(params postProcessParams, destRoot string) error { + coords := params.coords() + tempProtoSrcDir := params.protoDir() + if params.library.Name != commonProtosLibrary { if err := removeConflictingFiles(tempProtoSrcDir); err != nil { return err } @@ -297,7 +306,7 @@ func restructureModules(p postProcessParams, destRoot string) error { gapicTestDest := filepath.Join(destRoot, coords.GAPIC.ArtifactID, "src", "test") protoFilesDestDir := filepath.Join(destRoot, coords.Proto.ArtifactID, "src", "main", "proto") - if p.javaAPI.Monolithic { + if params.javaAPI.Monolithic { protoDest = filepath.Join(destRoot, "main", "java") grpcDest = filepath.Join(destRoot, "main", "java") gapicMainDest = filepath.Join(destRoot, "main") @@ -306,44 +315,44 @@ func restructureModules(p postProcessParams, destRoot string) error { } var actions []moveAction - if shouldGenerateProto(p.javaAPI) { + if shouldGenerateProto(params.javaAPI) { actions = append(actions, moveAction{ src: tempProtoSrcDir, dest: protoDest, description: "proto source", }) } - if shouldGenerateGRPC(p.javaAPI) { + if shouldGenerateGRPC(params.javaAPI) { actions = append(actions, moveAction{ - src: p.gRPCDir(), + src: params.gRPCDir(), dest: grpcDest, description: "grpc source", }) } - if shouldGenerateGAPIC(p.javaAPI) { + if shouldGenerateGAPIC(params.javaAPI) { actions = append(actions, []moveAction{ { - src: filepath.Join(p.gapicDir(), "src", "main"), + src: filepath.Join(params.gapicDir(), "src", "main"), dest: gapicMainDest, description: "gapic source", }, { - src: filepath.Join(p.gapicDir(), "src", "test"), + src: filepath.Join(params.gapicDir(), "src", "test"), dest: gapicTestDest, description: "gapic test", }, }...) } - if shouldGenerateResourceNames(p.javaAPI) { + if shouldGenerateResourceNames(params.javaAPI) { actions = append(actions, moveAction{ - src: filepath.Join(p.gapicDir(), "proto", "src", "main", "java"), + src: filepath.Join(params.gapicDir(), "proto", "src", "main", "java"), dest: protoDest, description: "resource name source", }) } - if p.includeSamples && shouldGenerateGAPIC(p.javaAPI) { + if params.includeSamples && shouldGenerateGAPIC(params.javaAPI) { actions = append(actions, moveAction{ - src: filepath.Join(p.gapicDir(), "samples", "snippets", "generated", "src", "main", "java"), + src: filepath.Join(params.gapicDir(), "samples", "snippets", "generated", "src", "main", "java"), dest: filepath.Join(destRoot, "samples", "snippets", "generated"), description: "samples", }) @@ -352,8 +361,8 @@ func restructureModules(p postProcessParams, destRoot string) error { return err } // Copy proto files to proto-*/src/main/proto - if shouldGenerateProto(p.javaAPI) { - if err := copyProtos(p.protosToCopy, protoFilesDestDir); err != nil { + if shouldGenerateProto(params.javaAPI) { + if err := copyProtos(params.protosToCopy, protoFilesDestDir); err != nil { return fmt.Errorf("failed to copy proto files: %w", err) } } diff --git a/internal/librarian/java/postprocess_test.go b/internal/librarian/java/postprocess_test.go index 97b1768a103..91cac3fd177 100644 --- a/internal/librarian/java/postprocess_test.go +++ b/internal/librarian/java/postprocess_test.go @@ -97,7 +97,7 @@ func TestPostProcessAPI(t *testing.T) { } apiProtos := []string{filepath.Join(googleapisDir, "google/cloud/secretmanager/v1/service.proto")} api := &config.API{Path: "google/cloud/secretmanager/v1"} - p := postProcessParams{ + params := postProcessParams{ cfg: &config.Config{ Libraries: []*config.Library{ {Name: "google-cloud-java", Version: "1.2.3"}, @@ -126,7 +126,7 @@ func TestPostProcessAPI(t *testing.T) { includeSamples: true, javaAPI: &config.JavaAPI{}, } - if err := postProcessAPI(t.Context(), p); err != nil { + if err := postProcessAPI(t.Context(), params); err != nil { t.Fatal(err) } @@ -198,7 +198,7 @@ func TestRestructureModules(t *testing.T) { protoPath := filepath.Join(googleapisDir, "google", "cloud", "secretmanager", "v1", "service.proto") additionalProtoPath := filepath.Join(googleapisDir, "google", "cloud", "oslogin", "common", "common.proto") - p := postProcessParams{ + params := postProcessParams{ outDir: tmpDir, library: &config.Library{ Name: libraryID, @@ -222,7 +222,7 @@ func TestRestructureModules(t *testing.T) { javaAPI: &config.JavaAPI{}, } destRoot := filepath.Join(tmpDir, "dest") - if err := restructureModules(p, destRoot); err != nil { + if err := restructureModules(params, destRoot); err != nil { t.Fatal(err) } @@ -253,7 +253,7 @@ func TestRestructureModules_CommonProtos(t *testing.T) { tmpDir := t.TempDir() apiBase := "v1" setupLocationProtoFile(t, tmpDir, apiBase) - p := postProcessParams{ + params := postProcessParams{ outDir: tmpDir, library: &config.Library{ Name: commonProtosLibrary, @@ -269,7 +269,7 @@ func TestRestructureModules_CommonProtos(t *testing.T) { }, } destRoot := filepath.Join(tmpDir, "dest") - if err := restructureModules(p, destRoot); err != nil { + if err := restructureModules(params, destRoot); err != nil { t.Fatal(err) } wantPath := filepath.Join(destRoot, "proto-google-common-protos", "src", "main", "java", "com", "google", "cloud", "location", "LocationsProto.java") @@ -283,7 +283,7 @@ func TestRestructureModules_ShouldRemoveClasses(t *testing.T) { tmpDir := t.TempDir() apiBase := "v1" setupLocationProtoFile(t, tmpDir, apiBase) - p := postProcessParams{ + params := postProcessParams{ outDir: tmpDir, library: &config.Library{ Name: "secretmanager", @@ -297,7 +297,7 @@ func TestRestructureModules_ShouldRemoveClasses(t *testing.T) { javaAPI: &config.JavaAPI{}, } destRoot := filepath.Join(tmpDir, "dest") - if err := restructureModules(p, destRoot); err != nil { + if err := restructureModules(params, destRoot); err != nil { t.Fatal(err) } wantPath := filepath.Join(destRoot, "proto-google-cloud-secretmanager-v1", "src", "main", "java", "com", "google", "cloud", "location", "LocationsProto.java") @@ -340,7 +340,7 @@ func TestRestructureModules_SamplesDisabled(t *testing.T) { t.Fatal(err) } - p := postProcessParams{ + params := postProcessParams{ outDir: tmpDir, library: &config.Library{ Name: libraryID, @@ -354,7 +354,7 @@ func TestRestructureModules_SamplesDisabled(t *testing.T) { javaAPI: &config.JavaAPI{}, } destRoot := filepath.Join(tmpDir, "dest") - if err := restructureModules(p, destRoot); err != nil { + if err := restructureModules(params, destRoot); err != nil { t.Fatal(err) } // Verify sample file location DOES NOT exist @@ -394,7 +394,7 @@ func TestRestructureModules_Monolithic(t *testing.T) { if err := os.WriteFile(protoFile, []byte("public class Proto {}"), 0644); err != nil { t.Fatal(err) } - p := postProcessParams{ + params := postProcessParams{ outDir: tmpDir, library: &config.Library{ Name: libraryID, @@ -410,7 +410,7 @@ func TestRestructureModules_Monolithic(t *testing.T) { }, } destRoot := filepath.Join(tmpDir, "dest", "src") - if err := restructureModules(p, destRoot); err != nil { + if err := restructureModules(params, destRoot); err != nil { t.Fatal(err) } @@ -451,7 +451,7 @@ func TestPostProcessAPI_SkipHeaders(t *testing.T) { t.Fatal(err) } - p := postProcessParams{ + params := postProcessParams{ outDir: outdir, apiBase: apiBase, library: &config.Library{Java: &config.JavaModule{}}, @@ -462,10 +462,10 @@ func TestPostProcessAPI_SkipHeaders(t *testing.T) { if err := os.WriteFile(headerFile, []byte(test.altHeader), 0644); err != nil { t.Fatal(err) } - p.library.Java.AlternateHeaders = "header.txt" + params.library.Java.AlternateHeaders = "header.txt" } - if err := addHeaders(p, []string{gRPCDir}); err != nil { + if err := addHeaders(params, []string{gRPCDir}); err != nil { t.Fatal(err) } @@ -502,8 +502,9 @@ func TestCopyProtos_Success(t *testing.T) { func TestCopyProtos_ErrorCase(t *testing.T) { t.Parallel() destDir := t.TempDir() - if err := copyProtos([]protoFileToCopy{{absolutePath: "/other/path/proto.proto", relativePath: "other/path/proto.proto"}}, destDir); err == nil { - t.Error("expected error for proto not in googleapisDir, got nil") + err := copyProtos([]protoFileToCopy{{absolutePath: "/other/path/proto.proto", relativePath: "other/path/proto.proto"}}, destDir) + if !errors.Is(err, fs.ErrNotExist) { + t.Errorf("copyProtos() error = %v, wantErr %v", err, fs.ErrNotExist) } } @@ -787,8 +788,9 @@ func TestRunOwlBot_Error(t *testing.T) { library := &config.Library{ Java: &config.JavaModule{}, } - if err := runOwlBot(t.Context(), library, outDir, ""); err == nil { - t.Error("expected error due to missing templates directory, got nil") + err := runOwlBot(t.Context(), library, outDir, "") + if !errors.Is(err, errTemplatesMissing) { + t.Errorf("runOwlBot() error = %v, wantErr %v", err, errTemplatesMissing) } if _, err := os.Stat(sDir); !errors.Is(err, fs.ErrNotExist) { t.Errorf("expected staging directory %s to be removed on error, but it still exists (err: %v)", sDir, err) @@ -798,7 +800,7 @@ func TestRunOwlBot_Error(t *testing.T) { func TestAddMissingHeaders(t *testing.T) { for _, test := range []struct { name string - p postProcessParams + params postProcessParams filename string content string wantModified bool @@ -827,7 +829,7 @@ func TestAddMissingHeaders(t *testing.T) { }, { name: "alternate header", - p: postProcessParams{ + params: postProcessParams{ library: &config.Library{ Java: &config.JavaModule{ AlternateHeaders: "header.txt", @@ -848,10 +850,10 @@ func TestAddMissingHeaders(t *testing.T) { t.Fatal(err) } - p := test.p - p.outDir = tmpDir - if p.library != nil && p.library.Java != nil && p.library.Java.AlternateHeaders != "" { - headerPath := filepath.Join(tmpDir, p.library.Java.AlternateHeaders) + params := test.params + params.outDir = tmpDir + if params.library != nil && params.library.Java != nil && params.library.Java.AlternateHeaders != "" { + headerPath := filepath.Join(tmpDir, params.library.Java.AlternateHeaders) if err := os.MkdirAll(filepath.Dir(headerPath), 0755); err != nil { t.Fatal(err) } @@ -860,7 +862,7 @@ func TestAddMissingHeaders(t *testing.T) { } } - if err := addMissingHeaders(p, tmpDir); err != nil { + if err := addMissingHeaders(params, tmpDir); err != nil { t.Fatal(err) } @@ -879,7 +881,7 @@ func TestAddMissingHeaders(t *testing.T) { func TestAddMissingHeaders_AlternateHeadersError(t *testing.T) { t.Parallel() tmpDir := t.TempDir() - p := postProcessParams{ + params := postProcessParams{ outDir: tmpDir, library: &config.Library{ Java: &config.JavaModule{ @@ -887,8 +889,9 @@ func TestAddMissingHeaders_AlternateHeadersError(t *testing.T) { }, }, } - if err := addMissingHeaders(p, tmpDir); err == nil { - t.Error("expected error for missing alternate headers file, got nil") + err := addMissingHeaders(params, tmpDir) + if !errors.Is(err, fs.ErrNotExist) { + t.Errorf("addMissingHeaders() error = %v, wantErr %v", err, fs.ErrNotExist) } } @@ -908,7 +911,7 @@ func TestCopyFiles(t *testing.T) { if err := os.WriteFile(fullSrcPath, []byte(content), 0644); err != nil { t.Fatal(err) } - p := postProcessParams{ + params := postProcessParams{ outDir: outdir, apiBase: apiBase, javaAPI: &config.JavaAPI{ @@ -920,7 +923,7 @@ func TestCopyFiles(t *testing.T) { }, }, } - if err := copyFiles(p); err != nil { + if err := copyFiles(params); err != nil { t.Fatal(err) } // Verify copy @@ -944,7 +947,7 @@ func TestCopyFiles_Error(t *testing.T) { t.Parallel() outdir := t.TempDir() apiBase := "v1" - p := postProcessParams{ + params := postProcessParams{ outDir: outdir, apiBase: apiBase, javaAPI: &config.JavaAPI{ @@ -956,8 +959,9 @@ func TestCopyFiles_Error(t *testing.T) { }, }, } - if err := copyFiles(p); err == nil { - t.Error("copyFiles() error = nil, want error for non-existent source") + err := copyFiles(params) + if !errors.Is(err, fs.ErrNotExist) { + t.Errorf("copyFiles() error = %v, wantErr %v", err, fs.ErrNotExist) } } From 6f862aaf678698b0e2ccd90674d7acd8105a0f98 Mon Sep 17 00:00:00 2001 From: Sofia Leon Date: Sat, 6 Jun 2026 00:09:54 +0000 Subject: [PATCH 10/14] chore: split test cases into a separate function --- internal/librarian/java/postprocess_test.go | 54 +++++++++++++++------ 1 file changed, 40 insertions(+), 14 deletions(-) diff --git a/internal/librarian/java/postprocess_test.go b/internal/librarian/java/postprocess_test.go index 91cac3fd177..9d978d9f009 100644 --- a/internal/librarian/java/postprocess_test.go +++ b/internal/librarian/java/postprocess_test.go @@ -432,14 +432,13 @@ func TestPostProcessAPI_SkipHeaders(t *testing.T) { for _, test := range []struct { name string monolithic bool - altHeader string wantHeader string }{ - {"default adds header", false, "", "/*\n * Copyright"}, - {"monolithic skips header", true, "", "package"}, - {"alternate adds alt header", false, "/* Alternate */\n", "/* Alternate */"}, + {"default adds header", false, "/*\n * Copyright"}, + {"monolithic skips header", true, "package"}, } { t.Run(test.name, func(t *testing.T) { + t.Parallel() outdir := t.TempDir() apiBase := "v1" gRPCDir := filepath.Join(outdir, apiBase, "grpc") @@ -450,25 +449,15 @@ func TestPostProcessAPI_SkipHeaders(t *testing.T) { if err := os.WriteFile(grpcFile, []byte("package com.test;"), 0644); err != nil { t.Fatal(err) } - params := postProcessParams{ outDir: outdir, apiBase: apiBase, library: &config.Library{Java: &config.JavaModule{}}, javaAPI: &config.JavaAPI{Monolithic: test.monolithic}, } - if test.altHeader != "" { - headerFile := filepath.Join(outdir, "header.txt") - if err := os.WriteFile(headerFile, []byte(test.altHeader), 0644); err != nil { - t.Fatal(err) - } - params.library.Java.AlternateHeaders = "header.txt" - } - if err := addHeaders(params, []string{gRPCDir}); err != nil { t.Fatal(err) } - got, err := os.ReadFile(grpcFile) if err != nil { t.Fatal(err) @@ -480,6 +469,43 @@ func TestPostProcessAPI_SkipHeaders(t *testing.T) { } } +func TestPostProcessAPI_AlternateHeaders(t *testing.T) { + t.Parallel() + outdir := t.TempDir() + apiBase := "v1" + gRPCDir := filepath.Join(outdir, apiBase, "grpc") + if err := os.MkdirAll(gRPCDir, 0755); err != nil { + t.Fatal(err) + } + grpcFile := filepath.Join(gRPCDir, "GRPCFile.java") + if err := os.WriteFile(grpcFile, []byte("package com.test;"), 0644); err != nil { + t.Fatal(err) + } + params := postProcessParams{ + outDir: outdir, + apiBase: apiBase, + library: &config.Library{Java: &config.JavaModule{}}, + javaAPI: &config.JavaAPI{}, + } + altHeader := "/* Alternate */\n" + headerFile := filepath.Join(outdir, "header.txt") + if err := os.WriteFile(headerFile, []byte(altHeader), 0644); err != nil { + t.Fatal(err) + } + params.library.Java.AlternateHeaders = "header.txt" + if err := addHeaders(params, []string{gRPCDir}); err != nil { + t.Fatal(err) + } + got, err := os.ReadFile(grpcFile) + if err != nil { + t.Fatal(err) + } + wantHeader := "/* Alternate */" + if !bytes.HasPrefix(got, []byte(wantHeader)) { + t.Errorf("mismatch got = %q, want %q", got, wantHeader) + } +} + func TestCopyProtos_Success(t *testing.T) { t.Parallel() destDir := t.TempDir() From a98dd918cd0b19d6f46e1952ca882f1eb6981e15 Mon Sep 17 00:00:00 2001 From: Sofia Leon Date: Sat, 6 Jun 2026 00:10:21 +0000 Subject: [PATCH 11/14] refactor(java): simplify getLicenseText control flow --- internal/librarian/java/postprocess.go | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/internal/librarian/java/postprocess.go b/internal/librarian/java/postprocess.go index abbb53e66d4..e8ecad9308b 100644 --- a/internal/librarian/java/postprocess.go +++ b/internal/librarian/java/postprocess.go @@ -190,19 +190,19 @@ func addMissingHeaders(params postProcessParams, dir string) error { // the alternate_header property (a filepath). Otherwise it will grab the default license // header. func getLicenseText(params postProcessParams) ([]byte, error) { - if params.library != nil && params.library.Java != nil && params.library.Java.AlternateHeaders != "" { - headerPath := filepath.Join(params.outDir, params.library.Java.AlternateHeaders) - b, err := os.ReadFile(headerPath) - if err != nil { - return nil, fmt.Errorf("failed to read alternate header file %s: %w", headerPath, err) - } - if len(b) > 0 && b[len(b)-1] != '\n' { - b = append(b, '\n') - } - return b, nil + if params.library == nil || params.library.Java == nil || params.library.Java.AlternateHeaders == "" { + year := time.Now().Year() + return []byte(buildLicenseText(year)), nil + } + headerPath := filepath.Join(params.outDir, params.library.Java.AlternateHeaders) + b, err := os.ReadFile(headerPath) + if err != nil { + return nil, fmt.Errorf("failed to read alternate header file %s: %w", headerPath, err) + } + if len(b) > 0 && b[len(b)-1] != '\n' { + b = append(b, '\n') } - year := time.Now().Year() - return []byte(buildLicenseText(year)), nil + return b, nil } func copyFiles(params postProcessParams) error { From e32c63e14800c6665521979317b343ef69c6e9d6 Mon Sep 17 00:00:00 2001 From: Sofia Leon Date: Sat, 6 Jun 2026 00:10:49 +0000 Subject: [PATCH 12/14] docs(java): add comment explaining alternate header newline check --- internal/librarian/java/postprocess.go | 1 + 1 file changed, 1 insertion(+) diff --git a/internal/librarian/java/postprocess.go b/internal/librarian/java/postprocess.go index e8ecad9308b..03b7eec096d 100644 --- a/internal/librarian/java/postprocess.go +++ b/internal/librarian/java/postprocess.go @@ -199,6 +199,7 @@ func getLicenseText(params postProcessParams) ([]byte, error) { if err != nil { return nil, fmt.Errorf("failed to read alternate header file %s: %w", headerPath, err) } + // Ensure the alternate header ends with a newline before it is prepended. if len(b) > 0 && b[len(b)-1] != '\n' { b = append(b, '\n') } From 874f6f70af4abda7a07fcdd747ccf51ba3c2ae8a Mon Sep 17 00:00:00 2001 From: Sofia Leon Date: Tue, 9 Jun 2026 23:39:19 +0000 Subject: [PATCH 13/14] chore: remove redundant test and respond to comments --- internal/librarian/java/postprocess.go | 4 +- internal/librarian/java/postprocess_test.go | 97 +++++++-------------- 2 files changed, 33 insertions(+), 68 deletions(-) diff --git a/internal/librarian/java/postprocess.go b/internal/librarian/java/postprocess.go index 03b7eec096d..91f53f08711 100644 --- a/internal/librarian/java/postprocess.go +++ b/internal/librarian/java/postprocess.go @@ -186,8 +186,8 @@ func addMissingHeaders(params postProcessParams, dir string) error { }) } -// If a library has an alternate header file, this function will read the contents of -// the alternate_header property (a filepath). Otherwise it will grab the default license +// getLicenseText reads the contents of the alternate_header property (a filepath) +// if a library has an alternate header file. Otherwise it will grab the default license // header. func getLicenseText(params postProcessParams) ([]byte, error) { if params.library == nil || params.library.Java == nil || params.library.Java.AlternateHeaders == "" { diff --git a/internal/librarian/java/postprocess_test.go b/internal/librarian/java/postprocess_test.go index 9d978d9f009..5ba383ffda6 100644 --- a/internal/librarian/java/postprocess_test.go +++ b/internal/librarian/java/postprocess_test.go @@ -23,6 +23,7 @@ import ( "os" "path/filepath" "strings" + "time" "testing" @@ -469,43 +470,6 @@ func TestPostProcessAPI_SkipHeaders(t *testing.T) { } } -func TestPostProcessAPI_AlternateHeaders(t *testing.T) { - t.Parallel() - outdir := t.TempDir() - apiBase := "v1" - gRPCDir := filepath.Join(outdir, apiBase, "grpc") - if err := os.MkdirAll(gRPCDir, 0755); err != nil { - t.Fatal(err) - } - grpcFile := filepath.Join(gRPCDir, "GRPCFile.java") - if err := os.WriteFile(grpcFile, []byte("package com.test;"), 0644); err != nil { - t.Fatal(err) - } - params := postProcessParams{ - outDir: outdir, - apiBase: apiBase, - library: &config.Library{Java: &config.JavaModule{}}, - javaAPI: &config.JavaAPI{}, - } - altHeader := "/* Alternate */\n" - headerFile := filepath.Join(outdir, "header.txt") - if err := os.WriteFile(headerFile, []byte(altHeader), 0644); err != nil { - t.Fatal(err) - } - params.library.Java.AlternateHeaders = "header.txt" - if err := addHeaders(params, []string{gRPCDir}); err != nil { - t.Fatal(err) - } - got, err := os.ReadFile(grpcFile) - if err != nil { - t.Fatal(err) - } - wantHeader := "/* Alternate */" - if !bytes.HasPrefix(got, []byte(wantHeader)) { - t.Errorf("mismatch got = %q, want %q", got, wantHeader) - } -} - func TestCopyProtos_Success(t *testing.T) { t.Parallel() destDir := t.TempDir() @@ -824,34 +788,37 @@ func TestRunOwlBot_Error(t *testing.T) { } func TestAddMissingHeaders(t *testing.T) { + defaultHeader := buildLicenseText(time.Now().Year()) for _, test := range []struct { - name string - params postProcessParams - filename string - content string - wantModified bool + name string + params postProcessParams + filename string + content string + wantContent string }{ { - name: "file without header", - filename: "NoHeader.java", - content: "package com.example;", - wantModified: true, + name: "file without header", + filename: "NoHeader.java", + content: "package com.example;", + wantContent: defaultHeader + "package com.example;", }, { - name: "file with full header", - filename: "WithHeader.java", - content: "/* Licensed under the Apache License, Version 2.0 (the \"License\") */\npackage com.example;", + name: "file with full header", + filename: "WithHeader.java", + content: "/* Licensed under the Apache License, Version 2.0 (the \"License\") */\npackage com.example;", + wantContent: "/* Licensed under the Apache License, Version 2.0 (the \"License\") */\npackage com.example;", }, { - name: "file with partial header", - filename: "PartialHeader.java", - content: "/* Copyright 2024 Google LLC */\npackage com.example;", - wantModified: true, + name: "file with partial header", + filename: "PartialHeader.java", + content: "/* Copyright 2024 Google LLC */\npackage com.example;", + wantContent: defaultHeader + "/* Copyright 2024 Google LLC */\npackage com.example;", }, { - name: "non-java file", - filename: "test.txt", - content: "some text", + name: "non-java file", + filename: "test.txt", + content: "some text", + wantContent: "some text", }, { name: "alternate header", @@ -862,17 +829,16 @@ func TestAddMissingHeaders(t *testing.T) { }, }, }, - filename: "AlternateHeader.java", - content: "package com.example;", - wantModified: true, + filename: "AlternateHeader.java", + content: "package com.example;", + wantContent: "/* Alternate Header */\npackage com.example;", }, } { t.Run(test.name, func(t *testing.T) { t.Parallel() tmpDir := t.TempDir() path := filepath.Join(tmpDir, test.filename) - originalContent := []byte(test.content) - if err := os.WriteFile(path, originalContent, 0644); err != nil { + if err := os.WriteFile(path, []byte(test.content), 0644); err != nil { t.Fatal(err) } @@ -892,19 +858,18 @@ func TestAddMissingHeaders(t *testing.T) { t.Fatal(err) } - newContent, err := os.ReadFile(path) + got, err := os.ReadFile(path) if err != nil { t.Fatal(err) } - wasModified := !bytes.Equal(originalContent, newContent) - if wasModified != test.wantModified { - t.Errorf("modification status = %v, want %v", wasModified, test.wantModified) + if diff := cmp.Diff(test.wantContent, string(got)); diff != "" { + t.Errorf("mismatch (-want +got):\n%s", diff) } }) } } -func TestAddMissingHeaders_AlternateHeadersError(t *testing.T) { +func TestAddMissingHeaders_AlternateHeaders_Error(t *testing.T) { t.Parallel() tmpDir := t.TempDir() params := postProcessParams{ From b33a88dc9624ebc9bbd61ed0f2e68565dcb50d8e Mon Sep 17 00:00:00 2001 From: Sofia Leon Date: Mon, 15 Jun 2026 22:37:32 +0000 Subject: [PATCH 14/14] restore TestPostProcessAPI_AlternateHeaders and remove duplicate test case --- internal/librarian/java/postprocess_test.go | 50 +++++++++++++++------ 1 file changed, 37 insertions(+), 13 deletions(-) diff --git a/internal/librarian/java/postprocess_test.go b/internal/librarian/java/postprocess_test.go index 5ba383ffda6..970c90dcc05 100644 --- a/internal/librarian/java/postprocess_test.go +++ b/internal/librarian/java/postprocess_test.go @@ -470,6 +470,43 @@ func TestPostProcessAPI_SkipHeaders(t *testing.T) { } } +func TestPostProcessAPI_AlternateHeaders(t *testing.T) { + t.Parallel() + outdir := t.TempDir() + apiBase := "v1" + gRPCDir := filepath.Join(outdir, apiBase, "grpc") + if err := os.MkdirAll(gRPCDir, 0755); err != nil { + t.Fatal(err) + } + grpcFile := filepath.Join(gRPCDir, "GRPCFile.java") + if err := os.WriteFile(grpcFile, []byte("package com.test;"), 0644); err != nil { + t.Fatal(err) + } + params := postProcessParams{ + outDir: outdir, + apiBase: apiBase, + library: &config.Library{Java: &config.JavaModule{}}, + javaAPI: &config.JavaAPI{}, + } + altHeader := "/* Alternate */\n" + headerFile := filepath.Join(outdir, "header.txt") + if err := os.WriteFile(headerFile, []byte(altHeader), 0644); err != nil { + t.Fatal(err) + } + params.library.Java.AlternateHeaders = "header.txt" + if err := addHeaders(params, []string{gRPCDir}); err != nil { + t.Fatal(err) + } + got, err := os.ReadFile(grpcFile) + if err != nil { + t.Fatal(err) + } + wantHeader := "/* Alternate */" + if !bytes.HasPrefix(got, []byte(wantHeader)) { + t.Errorf("mismatch got = %q, want %q", got, wantHeader) + } +} + func TestCopyProtos_Success(t *testing.T) { t.Parallel() destDir := t.TempDir() @@ -820,19 +857,6 @@ func TestAddMissingHeaders(t *testing.T) { content: "some text", wantContent: "some text", }, - { - name: "alternate header", - params: postProcessParams{ - library: &config.Library{ - Java: &config.JavaModule{ - AlternateHeaders: "header.txt", - }, - }, - }, - filename: "AlternateHeader.java", - content: "package com.example;", - wantContent: "/* Alternate Header */\npackage com.example;", - }, } { t.Run(test.name, func(t *testing.T) { t.Parallel()