Eine aufbereitete Darstellung der Quelle

 
     
 
 
Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 

Benutzer

Quelle  paths_test.go   Sprache: unbekannt

 
Spracherkennung für: .go vermutete Sprache: Unknown {[0] [0] [0]} [Methode: Schwerpunktbildung, einfache Gewichte, sechs Dimensionen]

// Copyright 2015 Google Inc. All rights reserved.
//
// 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.

package android

import (
 "errors"
 "fmt"
 "reflect"
 "strconv"
 "strings"
 "testing"

 "github.com/google/blueprint/proptools"
)

type strsTestCase struct {
 in  []string
 out string
 err []error
}

var commonValidatePathTestCases = []strsTestCase{
 {
  in:  []string{""},
  out: "",
 },
 {
  in:  []string{"", ""},
  out: "",
 },
 {
  in:  []string{"a", ""},
  out: "a",
 },
 {
  in:  []string{"", "a"},
  out: "a",
 },
 {
  in:  []string{"", "a", ""},
  out: "a",
 },
 {
  in:  []string{"a/b"},
  out: "a/b",
 },
 {
  in:  []string{"a/b", "c"},
  out: "a/b/c",
 },
 {
  in:  []string{"a/.."},
  out: ".",
 },
 {
  in:  []string{"."},
  out: ".",
 },
 {
  in:  []string{".."},
  out: "",
  err: []error{errors.New("Path is outside directory: ..")},
 },
 {
  in:  []string{"../a"},
  out: "",
  err: []error{errors.New("Path is outside directory: ../a")},
 },
 {
  in:  []string{"b/../../a"},
  out: "",
  err: []error{errors.New("Path is outside directory: ../a")},
 },
 {
  in:  []string{"/a"},
  out: "",
  err: []error{errors.New("Path is outside directory: /a")},
 },
 {
  in:  []string{"a", "../b"},
  out: "",
  err: []error{errors.New("Path is outside directory: ../b")},
 },
 {
  in:  []string{"a", "b/../../c"},
  out: "",
  err: []error{errors.New("Path is outside directory: ../c")},
 },
 {
  in:  []string{"a", "./.."},
  out: "",
  err: []error{errors.New("Path is outside directory: ..")},
 },
}

var validateSafePathTestCases = append(commonValidatePathTestCases, []strsTestCase{
 {
  in:  []string{"$host/../$a"},
  out: "$a",
 },
}...)

var validatePathTestCases = append(commonValidatePathTestCases, []strsTestCase{
 {
  in:  []string{"$host/../$a"},
  out: "",
  err: []error{errors.New("Path contains invalid character($): $host/../$a")},
 },
 {
  in:  []string{"$host/.."},
  out: "",
  err: []error{errors.New("Path contains invalid character($): $host/..")},
 },
}...)

func TestValidateSafePath(t *testing.T) {
 for _, testCase := range validateSafePathTestCases {
  t.Run(strings.Join(testCase.in, ","), func(t *testing.T) {
   ctx := &configErrorWrapper{}
   out, err := validateSafePath(testCase.in...)
   if err != nil {
    reportPathError(ctx, err)
   }
   check(t, "validateSafePath", p(testCase.in), out, ctx.errors, testCase.out, testCase.err)
  })
 }
}

func TestValidatePath(t *testing.T) {
 for _, testCase := range validatePathTestCases {
  t.Run(strings.Join(testCase.in, ","), func(t *testing.T) {
   ctx := &configErrorWrapper{}
   out, err := validatePath(testCase.in...)
   if err != nil {
    reportPathError(ctx, err)
   }
   check(t, "validatePath", p(testCase.in), out, ctx.errors, testCase.out, testCase.err)
  })
 }
}

func TestOptionalPath(t *testing.T) {
 var path OptionalPath
 checkInvalidOptionalPath(t, path, "unknown")

 path = OptionalPathForPath(nil)
 checkInvalidOptionalPath(t, path, "unknown")

 path = InvalidOptionalPath("foo")
 checkInvalidOptionalPath(t, path, "foo")

 path = InvalidOptionalPath("")
 checkInvalidOptionalPath(t, path, "unknown")

 path = OptionalPathForPath(PathForTesting("path"))
 checkValidOptionalPath(t, path, "path")
}

func checkInvalidOptionalPath(t *testing.T, path OptionalPath, expectedInvalidReason string) {
 t.Helper()
 if path.Valid() {
  t.Errorf("Invalid OptionalPath should not be valid")
 }
 if path.InvalidReason() != expectedInvalidReason {
  t.Errorf("Wrong invalid reason: expected %q, got %q", expectedInvalidReason, path.InvalidReason())
 }
 if path.String() != "" {
  t.Errorf("Invalid OptionalPath String() should return \"\", not %q", path.String())
 }
 paths := path.AsPaths()
 if len(paths) != 0 {
  t.Errorf("Invalid OptionalPath AsPaths() should return empty Paths, not %q", paths)
 }
 defer func() {
  if r := recover(); r == nil {
   t.Errorf("Expected a panic when calling Path() on an uninitialized OptionalPath")
  }
 }()
 path.Path()
}

func checkValidOptionalPath(t *testing.T, path OptionalPath, expectedString string) {
 t.Helper()
 if !path.Valid() {
  t.Errorf("Initialized OptionalPath should not be invalid")
 }
 if path.InvalidReason() != "" {
  t.Errorf("Initialized OptionalPath should not have an invalid reason, got: %q", path.InvalidReason())
 }
 if path.String() != expectedString {
  t.Errorf("Initialized OptionalPath String() should return %q, not %q", expectedString, path.String())
 }
 paths := path.AsPaths()
 if len(paths) != 1 {
  t.Errorf("Initialized OptionalPath AsPaths() should return Paths with length 1, not %q", paths)
 }
 path.Path()
}

func check(t *testing.T, testType, testString string,
 got interface{}, err []error,
 expected interface{}, expectedErr []error) {
 t.Helper()

 printedTestCase := false
 e := func(s string, expected, got interface{}) {
  t.Helper()
  if !printedTestCase {
   t.Errorf("test case %s: %s", testType, testString)
   printedTestCase = true
  }
  t.Errorf("incorrect %s", s)
  t.Errorf("  expected: %s", p(expected))
  t.Errorf("       got: %s", p(got))
 }

 if !reflect.DeepEqual(expectedErr, err) {
  e("errors:", expectedErr, err)
 }

 if !reflect.DeepEqual(expected, got) {
  e("output:", expected, got)
 }
}

func p(in interface{}) string {
 if v, ok := in.([]interface{}); ok {
  s := make([]string, len(v))
  for i := range v {
   s[i] = fmt.Sprintf("%#v", v[i])
  }
  return "[" + strings.Join(s, ", ") + "]"
 } else {
  return fmt.Sprintf("%#v", in)
 }
}

func pathTestConfig(buildDir string) Config {
 return TestConfig(buildDir, nil, "", nil)
}

func TestPathForModuleInstall(t *testing.T) {
 testConfig := pathTestConfig("")

 hostTarget := Target{Os: Linux, Arch: Arch{ArchType: X86}}
 deviceTarget := Target{Os: Android, Arch: Arch{ArchType: Arm64}}

 testCases := []struct {
  name         string
  ctx          *testModuleInstallPathContext
  in           []string
  out          string
  partitionDir string
 }{
  {
   name: "host binary",
   ctx: &testModuleInstallPathContext{
    baseModuleContext: baseModuleContext{
     archModuleContext: archModuleContext{
      os:     hostTarget.Os,
      target: hostTarget,
     },
    },
   },
   in:           []string{"bin", "my_test"},
   out:          "host/linux-x86/bin/my_test",
   partitionDir: "host/linux-x86",
  },

  {
   name: "system binary",
   ctx: &testModuleInstallPathContext{
    baseModuleContext: baseModuleContext{
     archModuleContext: archModuleContext{
      os:     deviceTarget.Os,
      target: deviceTarget,
     },
    },
   },
   in:           []string{"bin", "my_test"},
   out:          "target/product/test_device/system/bin/my_test",
   partitionDir: "target/product/test_device/system",
  },
  {
   name: "vendor binary",
   ctx: &testModuleInstallPathContext{
    baseModuleContext: baseModuleContext{
     archModuleContext: archModuleContext{
      os:     deviceTarget.Os,
      target: deviceTarget,
     },
     earlyModuleContext: earlyModuleContext{
      kind: socSpecificModule,
     },
    },
   },
   in:           []string{"bin", "my_test"},
   out:          "target/product/test_device/vendor/bin/my_test",
   partitionDir: "target/product/test_device/vendor",
  },
  {
   name: "odm binary",
   ctx: &testModuleInstallPathContext{
    baseModuleContext: baseModuleContext{
     archModuleContext: archModuleContext{
      os:     deviceTarget.Os,
      target: deviceTarget,
     },
     earlyModuleContext: earlyModuleContext{
      kind: deviceSpecificModule,
     },
    },
   },
   in:           []string{"bin", "my_test"},
   out:          "target/product/test_device/odm/bin/my_test",
   partitionDir: "target/product/test_device/odm",
  },
  {
   name: "product binary",
   ctx: &testModuleInstallPathContext{
    baseModuleContext: baseModuleContext{
     archModuleContext: archModuleContext{
      os:     deviceTarget.Os,
      target: deviceTarget,
     },
     earlyModuleContext: earlyModuleContext{
      kind: productSpecificModule,
     },
    },
   },
   in:           []string{"bin", "my_test"},
   out:          "target/product/test_device/product/bin/my_test",
   partitionDir: "target/product/test_device/product",
  },
  {
   name: "system_ext binary",
   ctx: &testModuleInstallPathContext{
    baseModuleContext: baseModuleContext{
     archModuleContext: archModuleContext{
      os:     deviceTarget.Os,
      target: deviceTarget,
     },
     earlyModuleContext: earlyModuleContext{
      kind: systemExtSpecificModule,
     },
    },
   },
   in:           []string{"bin", "my_test"},
   out:          "target/product/test_device/system_ext/bin/my_test",
   partitionDir: "target/product/test_device/system_ext",
  },
  {
   name: "root binary",
   ctx: &testModuleInstallPathContext{
    baseModuleContext: baseModuleContext{
     archModuleContext: archModuleContext{
      os:     deviceTarget.Os,
      target: deviceTarget,
     },
    },
    inRoot: true,
   },
   in:           []string{"my_test"},
   out:          "target/product/test_device/root/my_test",
   partitionDir: "target/product/test_device/root",
  },
  {
   name: "recovery binary",
   ctx: &testModuleInstallPathContext{
    baseModuleContext: baseModuleContext{
     archModuleContext: archModuleContext{
      os:     deviceTarget.Os,
      target: deviceTarget,
     },
    },
    inRecovery: true,
   },
   in:           []string{"bin/my_test"},
   out:          "target/product/test_device/recovery/root/system/bin/my_test",
   partitionDir: "target/product/test_device/recovery/root/system",
  },
  {
   name: "recovery root binary",
   ctx: &testModuleInstallPathContext{
    baseModuleContext: baseModuleContext{
     archModuleContext: archModuleContext{
      os:     deviceTarget.Os,
      target: deviceTarget,
     },
    },
    inRecovery: true,
    inRoot:     true,
   },
   in:           []string{"my_test"},
   out:          "target/product/test_device/recovery/root/my_test",
   partitionDir: "target/product/test_device/recovery/root",
  },

  {
   name: "ramdisk binary",
   ctx: &testModuleInstallPathContext{
    baseModuleContext: baseModuleContext{
     archModuleContext: archModuleContext{
      os:     deviceTarget.Os,
      target: deviceTarget,
     },
    },
    inRamdisk: true,
   },
   in:           []string{"my_test"},
   out:          "target/product/test_device/ramdisk/system/my_test",
   partitionDir: "target/product/test_device/ramdisk/system",
  },
  {
   name: "ramdisk root binary",
   ctx: &testModuleInstallPathContext{
    baseModuleContext: baseModuleContext{
     archModuleContext: archModuleContext{
      os:     deviceTarget.Os,
      target: deviceTarget,
     },
    },
    inRamdisk: true,
    inRoot:    true,
   },
   in:           []string{"my_test"},
   out:          "target/product/test_device/ramdisk/my_test",
   partitionDir: "target/product/test_device/ramdisk",
  },
  {
   name: "vendor_ramdisk binary",
   ctx: &testModuleInstallPathContext{
    baseModuleContext: baseModuleContext{
     archModuleContext: archModuleContext{
      os:     deviceTarget.Os,
      target: deviceTarget,
     },
    },
    inVendorRamdisk: true,
   },
   in:           []string{"my_test"},
   out:          "target/product/test_device/vendor_ramdisk/system/my_test",
   partitionDir: "target/product/test_device/vendor_ramdisk/system",
  },
  {
   name: "vendor_ramdisk root binary",
   ctx: &testModuleInstallPathContext{
    baseModuleContext: baseModuleContext{
     archModuleContext: archModuleContext{
      os:     deviceTarget.Os,
      target: deviceTarget,
     },
    },
    inVendorRamdisk: true,
    inRoot:          true,
   },
   in:           []string{"my_test"},
   out:          "target/product/test_device/vendor_ramdisk/my_test",
   partitionDir: "target/product/test_device/vendor_ramdisk",
  },
  {
   name: "debug_ramdisk binary",
   ctx: &testModuleInstallPathContext{
    baseModuleContext: baseModuleContext{
     archModuleContext: archModuleContext{
      os:     deviceTarget.Os,
      target: deviceTarget,
     },
    },
    inDebugRamdisk: true,
   },
   in:           []string{"my_test"},
   out:          "target/product/test_device/debug_ramdisk/my_test",
   partitionDir: "target/product/test_device/debug_ramdisk",
  },
  {
   name: "system native test binary",
   ctx: &testModuleInstallPathContext{
    baseModuleContext: baseModuleContext{
     archModuleContext: archModuleContext{
      os:     deviceTarget.Os,
      target: deviceTarget,
     },
    },
    inData: true,
   },
   in:           []string{"nativetest", "my_test"},
   out:          "target/product/test_device/data/nativetest/my_test",
   partitionDir: "target/product/test_device/data",
  },
  {
   name: "vendor native test binary",
   ctx: &testModuleInstallPathContext{
    baseModuleContext: baseModuleContext{
     archModuleContext: archModuleContext{
      os:     deviceTarget.Os,
      target: deviceTarget,
     },
     earlyModuleContext: earlyModuleContext{
      kind: socSpecificModule,
     },
    },
    inData: true,
   },
   in:           []string{"nativetest", "my_test"},
   out:          "target/product/test_device/data/nativetest/my_test",
   partitionDir: "target/product/test_device/data",
  },
  {
   name: "odm native test binary",
   ctx: &testModuleInstallPathContext{
    baseModuleContext: baseModuleContext{
     archModuleContext: archModuleContext{
      os:     deviceTarget.Os,
      target: deviceTarget,
     },
     earlyModuleContext: earlyModuleContext{
      kind: deviceSpecificModule,
     },
    },
    inData: true,
   },
   in:           []string{"nativetest", "my_test"},
   out:          "target/product/test_device/data/nativetest/my_test",
   partitionDir: "target/product/test_device/data",
  },
  {
   name: "product native test binary",
   ctx: &testModuleInstallPathContext{
    baseModuleContext: baseModuleContext{
     archModuleContext: archModuleContext{
      os:     deviceTarget.Os,
      target: deviceTarget,
     },
     earlyModuleContext: earlyModuleContext{
      kind: productSpecificModule,
     },
    },
    inData: true,
   },
   in:           []string{"nativetest", "my_test"},
   out:          "target/product/test_device/data/nativetest/my_test",
   partitionDir: "target/product/test_device/data",
  },

  {
   name: "system_ext native test binary",
   ctx: &testModuleInstallPathContext{
    baseModuleContext: baseModuleContext{
     archModuleContext: archModuleContext{
      os:     deviceTarget.Os,
      target: deviceTarget,
     },
     earlyModuleContext: earlyModuleContext{
      kind: systemExtSpecificModule,
     },
    },
    inData: true,
   },
   in:           []string{"nativetest", "my_test"},
   out:          "target/product/test_device/data/nativetest/my_test",
   partitionDir: "target/product/test_device/data",
  },

  {
   name: "sanitized system binary",
   ctx: &testModuleInstallPathContext{
    baseModuleContext: baseModuleContext{
     archModuleContext: archModuleContext{
      os:     deviceTarget.Os,
      target: deviceTarget,
     },
    },
    inSanitizerDir: true,
   },
   in:           []string{"bin", "my_test"},
   out:          "target/product/test_device/data/asan/system/bin/my_test",
   partitionDir: "target/product/test_device/data/asan/system",
  },
  {
   name: "sanitized vendor binary",
   ctx: &testModuleInstallPathContext{
    baseModuleContext: baseModuleContext{
     archModuleContext: archModuleContext{
      os:     deviceTarget.Os,
      target: deviceTarget,
     },
     earlyModuleContext: earlyModuleContext{
      kind: socSpecificModule,
     },
    },
    inSanitizerDir: true,
   },
   in:           []string{"bin", "my_test"},
   out:          "target/product/test_device/data/asan/vendor/bin/my_test",
   partitionDir: "target/product/test_device/data/asan/vendor",
  },
  {
   name: "sanitized odm binary",
   ctx: &testModuleInstallPathContext{
    baseModuleContext: baseModuleContext{
     archModuleContext: archModuleContext{
      os:     deviceTarget.Os,
      target: deviceTarget,
     },
     earlyModuleContext: earlyModuleContext{
      kind: deviceSpecificModule,
     },
    },
    inSanitizerDir: true,
   },
   in:           []string{"bin", "my_test"},
   out:          "target/product/test_device/data/asan/odm/bin/my_test",
   partitionDir: "target/product/test_device/data/asan/odm",
  },
  {
   name: "sanitized product binary",
   ctx: &testModuleInstallPathContext{
    baseModuleContext: baseModuleContext{
     archModuleContext: archModuleContext{
      os:     deviceTarget.Os,
      target: deviceTarget,
     },
     earlyModuleContext: earlyModuleContext{
      kind: productSpecificModule,
     },
    },
    inSanitizerDir: true,
   },
   in:           []string{"bin", "my_test"},
   out:          "target/product/test_device/data/asan/product/bin/my_test",
   partitionDir: "target/product/test_device/data/asan/product",
  },

  {
   name: "sanitized system_ext binary",
   ctx: &testModuleInstallPathContext{
    baseModuleContext: baseModuleContext{
     archModuleContext: archModuleContext{
      os:     deviceTarget.Os,
      target: deviceTarget,
     },
     earlyModuleContext: earlyModuleContext{
      kind: systemExtSpecificModule,
     },
    },
    inSanitizerDir: true,
   },
   in:           []string{"bin", "my_test"},
   out:          "target/product/test_device/data/asan/system_ext/bin/my_test",
   partitionDir: "target/product/test_device/data/asan/system_ext",
  },

  {
   name: "sanitized system native test binary",
   ctx: &testModuleInstallPathContext{
    baseModuleContext: baseModuleContext{
     archModuleContext: archModuleContext{
      os:     deviceTarget.Os,
      target: deviceTarget,
     },
    },
    inData:         true,
    inSanitizerDir: true,
   },
   in:           []string{"nativetest", "my_test"},
   out:          "target/product/test_device/data/asan/data/nativetest/my_test",
   partitionDir: "target/product/test_device/data/asan/data",
  },
  {
   name: "sanitized vendor native test binary",
   ctx: &testModuleInstallPathContext{
    baseModuleContext: baseModuleContext{
     archModuleContext: archModuleContext{
      os:     deviceTarget.Os,
      target: deviceTarget,
     },
     earlyModuleContext: earlyModuleContext{
      kind: socSpecificModule,
     },
    },
    inData:         true,
    inSanitizerDir: true,
   },
   in:           []string{"nativetest", "my_test"},
   out:          "target/product/test_device/data/asan/data/nativetest/my_test",
   partitionDir: "target/product/test_device/data/asan/data",
  },
  {
   name: "sanitized odm native test binary",
   ctx: &testModuleInstallPathContext{
    baseModuleContext: baseModuleContext{
     archModuleContext: archModuleContext{
      os:     deviceTarget.Os,
      target: deviceTarget,
     },
     earlyModuleContext: earlyModuleContext{
      kind: deviceSpecificModule,
     },
    },
    inData:         true,
    inSanitizerDir: true,
   },
   in:           []string{"nativetest", "my_test"},
   out:          "target/product/test_device/data/asan/data/nativetest/my_test",
   partitionDir: "target/product/test_device/data/asan/data",
  },
  {
   name: "sanitized product native test binary",
   ctx: &testModuleInstallPathContext{
    baseModuleContext: baseModuleContext{
     archModuleContext: archModuleContext{
      os:     deviceTarget.Os,
      target: deviceTarget,
     },
     earlyModuleContext: earlyModuleContext{
      kind: productSpecificModule,
     },
    },
    inData:         true,
    inSanitizerDir: true,
   },
   in:           []string{"nativetest", "my_test"},
   out:          "target/product/test_device/data/asan/data/nativetest/my_test",
   partitionDir: "target/product/test_device/data/asan/data",
  },
  {
   name: "sanitized system_ext native test binary",
   ctx: &testModuleInstallPathContext{
    baseModuleContext: baseModuleContext{
     archModuleContext: archModuleContext{
      os:     deviceTarget.Os,
      target: deviceTarget,
     },
     earlyModuleContext: earlyModuleContext{
      kind: systemExtSpecificModule,
     },
    },
    inData:         true,
    inSanitizerDir: true,
   },
   in:           []string{"nativetest", "my_test"},
   out:          "target/product/test_device/data/asan/data/nativetest/my_test",
   partitionDir: "target/product/test_device/data/asan/data",
  }, {
   name: "device testcases",
   ctx: &testModuleInstallPathContext{
    baseModuleContext: baseModuleContext{
     archModuleContext: archModuleContext{
      os:     deviceTarget.Os,
      target: deviceTarget,
     },
    },
    inTestcases: true,
   },
   in:           []string{"my_test", "my_test_bin"},
   out:          "target/product/test_device/testcases/my_test/my_test_bin",
   partitionDir: "target/product/test_device/testcases",
  }, {
   name: "host testcases",
   ctx: &testModuleInstallPathContext{
    baseModuleContext: baseModuleContext{
     archModuleContext: archModuleContext{
      os:     hostTarget.Os,
      target: hostTarget,
     },
    },
    inTestcases: true,
   },
   in:           []string{"my_test", "my_test_bin"},
   out:          "host/linux-x86/testcases/my_test/my_test_bin",
   partitionDir: "host/linux-x86/testcases",
  }, {
   name: "forced host testcases",
   ctx: &testModuleInstallPathContext{
    baseModuleContext: baseModuleContext{
     archModuleContext: archModuleContext{
      os:     deviceTarget.Os,
      target: deviceTarget,
     },
    },
    inTestcases: true,
    forceOS:     &Linux,
    forceArch:   &X86,
   },
   in:           []string{"my_test", "my_test_bin"},
   out:          "host/linux-x86/testcases/my_test/my_test_bin",
   partitionDir: "host/linux-x86/testcases",
  },
 }

 for _, tc := range testCases {
  t.Run(tc.name, func(t *testing.T) {
   tc.ctx.baseModuleContext.config = testConfig
   output := PathForModuleInstall(tc.ctx, tc.in...)
   if output.basePath.path != tc.out {
    t.Errorf("unexpected path:\n got: %q\nwant: %q\n",
     output.basePath.path,
     tc.out)
   }
   if output.partitionDir != tc.partitionDir {
    t.Errorf("unexpected partitionDir:\n got: %q\nwant: %q\n",
     output.partitionDir, tc.partitionDir)
   }
  })
 }
}

func TestPathForModuleInstallRecoveryAsBoot(t *testing.T) {
 testConfig := pathTestConfig("")
 testConfig.TestProductVariables.BoardUsesRecoveryAsBoot = proptools.BoolPtr(true)
 testConfig.TestProductVariables.BoardMoveRecoveryResourcesToVendorBoot = proptools.BoolPtr(true)
 deviceTarget := Target{Os: Android, Arch: Arch{ArchType: Arm64}}

 testCases := []struct {
  name         string
  ctx          *testModuleInstallPathContext
  in           []string
  out          string
  partitionDir string
 }{
  {
   name: "ramdisk binary",
   ctx: &testModuleInstallPathContext{
    baseModuleContext: baseModuleContext{
     archModuleContext: archModuleContext{
      os:     deviceTarget.Os,
      target: deviceTarget,
     },
    },
    inRamdisk: true,
    inRoot:    true,
   },
   in:           []string{"my_test"},
   out:          "target/product/test_device/recovery/root/first_stage_ramdisk/my_test",
   partitionDir: "target/product/test_device/recovery/root/first_stage_ramdisk",
  },

  {
   name: "vendor_ramdisk binary",
   ctx: &testModuleInstallPathContext{
    baseModuleContext: baseModuleContext{
     archModuleContext: archModuleContext{
      os:     deviceTarget.Os,
      target: deviceTarget,
     },
    },
    inVendorRamdisk: true,
    inRoot:          true,
   },
   in:           []string{"my_test"},
   out:          "target/product/test_device/vendor_ramdisk/first_stage_ramdisk/my_test",
   partitionDir: "target/product/test_device/vendor_ramdisk/first_stage_ramdisk",
  },
 }

 for _, tc := range testCases {
  t.Run(tc.name, func(t *testing.T) {
   tc.ctx.baseModuleContext.config = testConfig
   output := PathForModuleInstall(tc.ctx, tc.in...)
   if output.basePath.path != tc.out {
    t.Errorf("unexpected path:\n got: %q\nwant: %q\n",
     output.basePath.path,
     tc.out)
   }
   if output.partitionDir != tc.partitionDir {
    t.Errorf("unexpected partitionDir:\n got: %q\nwant: %q\n",
     output.partitionDir, tc.partitionDir)
   }
  })
 }
}

func TestBaseDirForInstallPath(t *testing.T) {
 testConfig := pathTestConfig("")
 deviceTarget := Target{Os: Android, Arch: Arch{ArchType: Arm64}}

 ctx := &testModuleInstallPathContext{
  baseModuleContext: baseModuleContext{
   archModuleContext: archModuleContext{
    os:     deviceTarget.Os,
    target: deviceTarget,
   },
  },
 }
 ctx.baseModuleContext.config = testConfig

 actual := PathForModuleInstall(ctx, "foo", "bar")
 expectedBaseDir := "target/product/test_device/system"
 if actual.partitionDir != expectedBaseDir {
  t.Errorf("unexpected partitionDir:\n got: %q\nwant: %q\n", actual.partitionDir, expectedBaseDir)
 }
 expectedRelPath := "foo/bar"
 if actual.Rel() != expectedRelPath {
  t.Errorf("unexpected Rel():\n got: %q\nwant: %q\n", actual.Rel(), expectedRelPath)
 }

 actualAfterJoin := actual.Join(ctx, "baz")
 // partitionDir is preserved even after joining
 if actualAfterJoin.partitionDir != expectedBaseDir {
  t.Errorf("unexpected partitionDir after joining:\n got: %q\nwant: %q\n", actualAfterJoin.partitionDir, expectedBaseDir)
 }
 // Rel() is updated though
 expectedRelAfterJoin := "baz"
 if actualAfterJoin.Rel() != expectedRelAfterJoin {
  t.Errorf("unexpected Rel() after joining:\n got: %q\nwant: %q\n", actualAfterJoin.Rel(), expectedRelAfterJoin)
 }
}

func TestDirectorySortedPaths(t *testing.T) {
 config := TestConfig("out", nil, "", map[string][]byte{
  "Android.bp": nil,
  "a.txt":      nil,
  "a/txt":      nil,
  "a/b/c":      nil,
  "a/b/d":      nil,
  "b":          nil,
  "b/b.txt":    nil,
  "a/a.txt":    nil,
 })

 ctx := PathContextForTesting(config)

 makePaths := func() Paths {
  return Paths{
   PathForSource(ctx, "a.txt"),
   PathForSource(ctx, "a/txt"),
   PathForSource(ctx, "a/b/c"),
   PathForSource(ctx, "a/b/d"),
   PathForSource(ctx, "b"),
   PathForSource(ctx, "b/b.txt"),
   PathForSource(ctx, "a/a.txt"),
  }
 }

 expected := []string{
  "a.txt",
  "a/a.txt",
  "a/b/c",
  "a/b/d",
  "a/txt",
  "b",
  "b/b.txt",
 }

 paths := makePaths()
 reversePaths := ReversePaths(paths)

 sortedPaths := PathsToDirectorySortedPaths(paths)
 reverseSortedPaths := PathsToDirectorySortedPaths(reversePaths)

 if !reflect.DeepEqual(Paths(sortedPaths).Strings(), expected) {
  t.Fatalf("sorted paths:\n %#v\n != \n %#v", paths.Strings(), expected)
 }

 if !reflect.DeepEqual(Paths(reverseSortedPaths).Strings(), expected) {
  t.Fatalf("sorted reversed paths:\n %#v\n !=\n %#v", reversePaths.Strings(), expected)
 }

 expectedA := []string{
  "a/a.txt",
  "a/b/c",
  "a/b/d",
  "a/txt",
 }

 inA := sortedPaths.PathsInDirectory("a")
 if !reflect.DeepEqual(inA.Strings(), expectedA) {
  t.Errorf("FilesInDirectory(a):\n %#v\n != \n %#v", inA.Strings(), expectedA)
 }

 expectedA_B := []string{
  "a/b/c",
  "a/b/d",
 }

 inA_B := sortedPaths.PathsInDirectory("a/b")
 if !reflect.DeepEqual(inA_B.Strings(), expectedA_B) {
  t.Errorf("FilesInDirectory(a/b):\n %#v\n != \n %#v", inA_B.Strings(), expectedA_B)
 }

 expectedB := []string{
  "b/b.txt",
 }

 inB := sortedPaths.PathsInDirectory("b")
 if !reflect.DeepEqual(inB.Strings(), expectedB) {
  t.Errorf("FilesInDirectory(b):\n %#v\n != \n %#v", inA.Strings(), expectedA)
 }
}

func TestMaybeRel(t *testing.T) {
 testCases := []struct {
  name   string
  base   string
  target string
  out    string
  isRel  bool
 }{
  {
   name:   "normal",
   base:   "a/b/c",
   target: "a/b/c/d",
   out:    "d",
   isRel:  true,
  },
  {
   name:   "parent",
   base:   "a/b/c/d",
   target: "a/b/c",
   isRel:  false,
  },
  {
   name:   "not relative",
   base:   "a/b",
   target: "c/d",
   isRel:  false,
  },
  {
   name:   "abs1",
   base:   "/a",
   target: "a",
   isRel:  false,
  },
  {
   name:   "abs2",
   base:   "a",
   target: "/a",
   isRel:  false,
  },
 }

 for _, testCase := range testCases {
  t.Run(testCase.name, func(t *testing.T) {
   ctx := &configErrorWrapper{}
   out, isRel := MaybeRel(ctx, testCase.base, testCase.target)
   if len(ctx.errors) > 0 {
    t.Errorf("MaybeRel(..., %s, %s) reported unexpected errors %v",
     testCase.base, testCase.target, ctx.errors)
   }
   if isRel != testCase.isRel || out != testCase.out {
    t.Errorf("MaybeRel(..., %s, %s) want %v, %v got %v, %v",
     testCase.base, testCase.target, testCase.out, testCase.isRel, out, isRel)
   }
  })
 }
}

func TestPathForSource(t *testing.T) {
 testCases := []struct {
  name     string
  buildDir string
  src      string
  err      string
 }{
  {
   name:     "normal",
   buildDir: "out",
   src:      "a/b/c",
  },
  {
   name:     "abs",
   buildDir: "out",
   src:      "/a/b/c",
   err:      "is outside directory",
  },
  {
   name:     "in out dir",
   buildDir: "out",
   src:      "out/soong/a/b/c",
   err:      "is in output",
  },
 }

 funcs := []struct {
  name string
  f    func(ctx PathContext, pathComponents ...string) (SourcePath, error)
 }{
  {"pathForSource", pathForSource},
  {"safePathForSource", safePathForSource},
 }

 for _, f := range funcs {
  t.Run(f.name, func(t *testing.T) {
   for _, test := range testCases {
    t.Run(test.name, func(t *testing.T) {
     testConfig := pathTestConfig(test.buildDir)
     ctx := &configErrorWrapper{config: testConfig}
     _, err := f.f(ctx, test.src)
     if len(ctx.errors) > 0 {
      t.Fatalf("unexpected errors %v", ctx.errors)
     }
     if err != nil {
      if test.err == "" {
       t.Fatalf("unexpected error %q", err.Error())
      } else if !strings.Contains(err.Error(), test.err) {
       t.Fatalf("incorrect error, want substring %q got %q", test.err, err.Error())
      }
     } else {
      if test.err != "" {
       t.Fatalf("missing error %q", test.err)
      }
     }
    })
   }
  })
 }
}

type pathForModuleSrcTestModule struct {
 ModuleBase
 props struct {
  Srcs         []string `android:"path"`
  Exclude_srcs []string `android:"path"`

  Src *string `android:"path"`

  Module_handles_missing_deps bool
 }

 src string
 rel string

 srcs []string
 rels []string

 missingDeps []string
}

func pathForModuleSrcTestModuleFactory() Module {
 module := &pathForModuleSrcTestModule{}
 module.AddProperties(&module.props)
 InitAndroidModule(module)
 return module
}

func (p *pathForModuleSrcTestModule) GenerateAndroidBuildActions(ctx ModuleContext) {
 var srcs Paths
 if p.props.Module_handles_missing_deps {
  srcs, p.missingDeps = PathsAndMissingDepsForModuleSrcExcludes(ctx, p.props.Srcs, p.props.Exclude_srcs)
 } else {
  srcs = PathsForModuleSrcExcludes(ctx, p.props.Srcs, p.props.Exclude_srcs)
 }
 p.srcs = srcs.Strings()

 for _, src := range srcs {
  p.rels = append(p.rels, src.Rel())
 }

 if p.props.Src != nil {
  src := PathForModuleSrc(ctx, *p.props.Src)
  if src != nil {
   p.src = src.String()
   p.rel = src.Rel()
  }
 }

 if !p.props.Module_handles_missing_deps {
  p.missingDeps = ctx.GetMissingDependencies()
 }

 ctx.Build(pctx, BuildParams{
  Rule:   TouchRule,
  Output: PathForModuleOut(ctx, "output"),
 })
}

type pathForModuleSrcOutputFileProviderModule struct {
 ModuleBase
 props struct {
  Outs   []string
  Tagged []string
 }
}

func pathForModuleSrcOutputFileProviderModuleFactory() Module {
 module := &pathForModuleSrcOutputFileProviderModule{}
 module.AddProperties(&module.props)
 InitAndroidModule(module)
 return module
}

func (p *pathForModuleSrcOutputFileProviderModule) GenerateAndroidBuildActions(ctx ModuleContext) {
 var outs, taggedOuts Paths
 for _, out := range p.props.Outs {
  outs = append(outs, PathForModuleOut(ctx, out))
 }

 for _, tagged := range p.props.Tagged {
  taggedOuts = append(taggedOuts, PathForModuleOut(ctx, tagged))
 }

 ctx.SetOutputFiles(outs, "")
 ctx.SetOutputFiles(taggedOuts, ".tagged")
}

type pathForModuleSrcTestCase struct {
 name string
 bp   string
 srcs []string
 rels []string
 src  string
 rel  string

 // Make test specific preparations to the test fixture.
 preparer FixturePreparer

 // A test specific error handler.
 errorHandler FixtureErrorHandler
}

func testPathForModuleSrc(t *testing.T, tests []pathForModuleSrcTestCase) {
 for _, test := range tests {
  t.Run(test.name, func(t *testing.T) {
   fgBp := `
    filegroup {
     name: "a",
     srcs: ["src/a"],
    }
   `

   ofpBp := `
    output_file_provider {
     name: "b",
     outs: ["gen/b"],
     tagged: ["gen/c"],
    }
   `

   mockFS := MockFS{
    "fg/Android.bp":     []byte(fgBp),
    "foo/Android.bp":    []byte(test.bp),
    "ofp/Android.bp":    []byte(ofpBp),
    "fg/src/a":          nil,
    "foo/src/b":         nil,
    "foo/src/c":         nil,
    "foo/src/d":         nil,
    "foo/src/e/e":       nil,
    "foo/src_special/$": nil,
   }

   errorHandler := test.errorHandler
   if errorHandler == nil {
    errorHandler = FixtureExpectsNoErrors
   }

   result := GroupFixturePreparers(
    FixtureRegisterWithContext(func(ctx RegistrationContext) {
     ctx.RegisterModuleType("test", pathForModuleSrcTestModuleFactory)
     ctx.RegisterModuleType("output_file_provider", pathForModuleSrcOutputFileProviderModuleFactory)
    }),
    PrepareForTestWithFilegroup,
    PrepareForTestWithNamespace,
    mockFS.AddToFixture(),
    OptionalFixturePreparer(test.preparer),
   ).
    ExtendWithErrorHandler(errorHandler).
    RunTest(t)

   m := result.ModuleForTests(t, "foo", "").Module().(*pathForModuleSrcTestModule)

   AssertStringPathsRelativeToTopEquals(t, "srcs", result.Config, test.srcs, m.srcs)
   AssertStringPathsRelativeToTopEquals(t, "rels", result.Config, test.rels, m.rels)
   AssertStringPathRelativeToTopEquals(t, "src", result.Config, test.src, m.src)
   AssertStringPathRelativeToTopEquals(t, "rel", result.Config, test.rel, m.rel)
  })
 }
}

func TestPathsForModuleSrc(t *testing.T) {
 tests := []pathForModuleSrcTestCase{
  {
   name: "path",
   bp: `
   test {
    name: "foo",
    srcs: ["src/b"],
   }`,
   srcs: []string{"foo/src/b"},
   rels: []string{"src/b"},
  },
  {
   name: "glob",
   bp: `
   test {
    name: "foo",
    srcs: [
     "src/*",
     "src/e/*",
    ],
   }`,
   srcs: []string{"foo/src/b", "foo/src/c", "foo/src/d", "foo/src/e/e"},
   rels: []string{"src/b", "src/c", "src/d", "src/e/e"},
  },
  {
   name: "recursive glob",
   bp: `
   test {
    name: "foo",
    srcs: ["src/**/*"],
   }`,
   srcs: []string{"foo/src/b", "foo/src/c", "foo/src/d", "foo/src/e/e"},
   rels: []string{"src/b", "src/c", "src/d", "src/e/e"},
  },
  {
   name: "filegroup",
   bp: `
   test {
    name: "foo",
    srcs: [":a"],
   }`,
   srcs: []string{"fg/src/a"},
   rels: []string{"src/a"},
  },
  {
   name: "output file provider",
   bp: `
   test {
    name: "foo",
    srcs: [":b"],
   }`,
   srcs: []string{"out/soong/.intermediates/ofp/b/gen/b"},
   rels: []string{"gen/b"},
  },
  {
   name: "output file provider tagged",
   bp: `
   test {
    name: "foo",
    srcs: [":b{.tagged}"],
   }`,
   srcs: []string{"out/soong/.intermediates/ofp/b/gen/c"},
   rels: []string{"gen/c"},
  },
  {
   name: "output file provider with exclude",
   bp: `
   test {
    name: "foo",
    srcs: [":b", ":c"],
    exclude_srcs: [":c"]
   }
   output_file_provider {
    name: "c",
    outs: ["gen/c"],
   }`,
   srcs: []string{"out/soong/.intermediates/ofp/b/gen/b"},
   rels: []string{"gen/b"},
  },
  {
   name: "special characters glob",
   bp: `
   test {
    name: "foo",
    srcs: ["src_special/*"],
   }`,
   srcs: []string{"foo/src_special/$"},
   rels: []string{"src_special/$"},
  },
 }

 testPathForModuleSrc(t, tests)
}

func TestPathForModuleSrc(t *testing.T) {
 tests := []pathForModuleSrcTestCase{
  {
   name: "path",
   bp: `
   test {
    name: "foo",
    src: "src/b",
   }`,
   src: "foo/src/b",
   rel: "src/b",
  },
  {
   name: "glob",
   bp: `
   test {
    name: "foo",
    src: "src/e/*",
   }`,
   src: "foo/src/e/e",
   rel: "src/e/e",
  },
  {
   name: "filegroup",
   bp: `
   test {
    name: "foo",
    src: ":a",
   }`,
   src: "fg/src/a",
   rel: "src/a",
  },
  {
   name: "output file provider",
   bp: `
   test {
    name: "foo",
    src: ":b",
   }`,
   src: "out/soong/.intermediates/ofp/b/gen/b",
   rel: "gen/b",
  },
  {
   name: "output file provider tagged",
   bp: `
   test {
    name: "foo",
    src: ":b{.tagged}",
   }`,
   src: "out/soong/.intermediates/ofp/b/gen/c",
   rel: "gen/c",
  },
  {
   name: "special characters glob",
   bp: `
   test {
    name: "foo",
    src: "src_special/*",
   }`,
   src: "foo/src_special/$",
   rel: "src_special/$",
  },
  {
   // This test makes sure that an unqualified module name cannot contain characters that make
   // it appear as a qualified module name.
   name: "output file provider, invalid fully qualified name",
   bp: `
   test {
    name: "foo",
    src: "://other:b",
    srcs: ["://other:c"],
   }`,
   preparer: FixtureAddTextFile("other/Android.bp", `
    soong_namespace {}

    output_file_provider {
     name: "b",
     outs: ["gen/b"],
    }

    output_file_provider {
     name: "c",
     outs: ["gen/c"],
    }
   `),
   src:  "foo/:/other:b",
   rel:  ":/other:b",
   srcs: []string{"foo/:/other:c"},
   rels: []string{":/other:c"},
  },
  {
   name: "output file provider, missing fully qualified name",
   bp: `
   test {
    name: "foo",
    src: "//other:d",
    srcs: ["//other:e"],
   }`,
   errorHandler: FixtureExpectsAllErrorsToMatchAPattern([]string{
    `"foo" depends on undefined module "//other:d"`,
    `"foo" depends on undefined module "//other:e"`,
   }),
  },
  {
   name: "output file provider, fully qualified name",
   bp: `
   test {
    name: "foo",
    src: "//other:b",
    srcs: ["//other:c"],
   }`,
   src:  "out/soong/.intermediates/other/b/gen/b",
   rel:  "gen/b",
   srcs: []string{"out/soong/.intermediates/other/c/gen/c"},
   rels: []string{"gen/c"},
   preparer: FixtureAddTextFile("other/Android.bp", `
    soong_namespace {}

    output_file_provider {
     name: "b",
     outs: ["gen/b"],
    }

    output_file_provider {
     name: "c",
     outs: ["gen/c"],
    }
   `),
  },
 }

 testPathForModuleSrc(t, tests)
}

func TestPathsForModuleSrc_AllowMissingDependencies(t *testing.T) {
 bp := `
  test {
   name: "foo",
   srcs: [":a"],
   exclude_srcs: [":b"],
   src: ":c",
  }

  test {
   name: "bar",
   srcs: [":d"],
   exclude_srcs: [":e"],
   module_handles_missing_deps: true,
  }
 `

 result := GroupFixturePreparers(
  PrepareForTestWithAllowMissingDependencies,
  FixtureRegisterWithContext(func(ctx RegistrationContext) {
   ctx.RegisterModuleType("test", pathForModuleSrcTestModuleFactory)
  }),
  FixtureWithRootAndroidBp(bp),
 ).RunTest(t)

 foo := result.ModuleForTests(t, "foo", "").Module().(*pathForModuleSrcTestModule)

 AssertArrayString(t, "foo missing deps", []string{"a", "b", "c"}, foo.missingDeps)
 AssertArrayString(t, "foo srcs", []string{}, foo.srcs)
 AssertStringEquals(t, "foo src", "Missing_PathForModuleSrc_file", foo.src)

 bar := result.ModuleForTests(t, "bar", "").Module().(*pathForModuleSrcTestModule)

 AssertArrayString(t, "bar missing deps", []string{"d", "e"}, bar.missingDeps)
 AssertArrayString(t, "bar srcs", []string{}, bar.srcs)
}

func TestPathRelativeToTop(t *testing.T) {
 testConfig := pathTestConfig("/tmp/build/top")
 deviceTarget := Target{Os: Android, Arch: Arch{ArchType: Arm64}}

 ctx := &testModuleInstallPathContext{
  baseModuleContext: baseModuleContext{
   archModuleContext: archModuleContext{
    os:     deviceTarget.Os,
    target: deviceTarget,
   },
  },
 }
 ctx.baseModuleContext.config = testConfig

 t.Run("install for soong", func(t *testing.T) {
  p := PathForModuleInstall(ctx, "install/path")
  AssertPathRelativeToTopEquals(t, "install path for soong", "out/target/product/test_device/system/install/path", p)
 })
 t.Run("install for make", func(t *testing.T) {
  p := PathForModuleInstall(ctx, "install/path")
  p.makePath = true
  AssertPathRelativeToTopEquals(t, "install path for make", "out/target/product/test_device/system/install/path", p)
 })
 t.Run("output", func(t *testing.T) {
  p := PathForOutput(ctx, "output/path")
  AssertPathRelativeToTopEquals(t, "output path", "out/soong/output/path", p)
 })
 t.Run("source", func(t *testing.T) {
  p := PathForSource(ctx, "source/path")
  AssertPathRelativeToTopEquals(t, "source path", "source/path", p)
 })
 t.Run("mixture", func(t *testing.T) {
  paths := Paths{
   PathForModuleInstall(ctx, "install/path"),
   PathForOutput(ctx, "output/path"),
   PathForSource(ctx, "source/path"),
  }

  expected := []string{
   "out/target/product/test_device/system/install/path",
   "out/soong/output/path",
   "source/path",
  }
  AssertPathsRelativeToTopEquals(t, "mixture", expected, paths)
 })
}

func TestDirectoryPathIsIncompatibleWithPath(t *testing.T) {
 d := (DirectoryPath)(&directoryPath{})
 _, ok := d.(Path)
 AssertBoolEquals(t, "directoryPath shouldn't implement Path", ok, false)
}

func ExampleOutputPath_ReplaceExtension() {
 ctx := &configErrorWrapper{
  config: TestConfig("out", nil, "", nil),
 }
 p := PathForOutput(ctx, "system/framework").Join(ctx, "boot.art")
 p2 := p.ReplaceExtension(ctx, "oat")
 fmt.Println(p, p2)
 fmt.Println(p.Rel(), p2.Rel())

 // Output:
 // out/soong/system/framework/boot.art out/soong/system/framework/boot.oat
 // boot.art boot.oat
}

func ExampleOutputPath_InSameDir() {
 ctx := &configErrorWrapper{
  config: TestConfig("out", nil, "", nil),
 }
 p := PathForOutput(ctx, "system/framework").Join(ctx, "boot.art")
 p2 := p.InSameDir(ctx, "oat", "arm", "boot.vdex")
 fmt.Println(p, p2)
 fmt.Println(p.Rel(), p2.Rel())

 // Output:
 // out/soong/system/framework/boot.art out/soong/system/framework/oat/arm/boot.vdex
 // boot.art oat/arm/boot.vdex
}

func BenchmarkFirstUniquePaths(b *testing.B) {
 implementations := []struct {
  name string
  f    func(Paths) Paths
 }{
  {
   name: "list",
   f:    firstUniquePathsList,
  },
  {
   name: "map",
   f:    firstUniquePathsMap,
  },
 }
 const maxSize = 1024
 uniquePaths := make(Paths, maxSize)
 for i := range uniquePaths {
  uniquePaths[i] = PathForTesting(strconv.Itoa(i))
 }
 samePath := make(Paths, maxSize)
 for i := range samePath {
  samePath[i] = uniquePaths[0]
 }

 f := func(b *testing.B, imp func(Paths) Paths, paths Paths) {
  for i := 0; i < b.N; i++ {
   b.ReportAllocs()
   paths = append(Paths(nil), paths...)
   imp(paths)
  }
 }

 for n := 1; n <= maxSize; n <<= 1 {
  b.Run(strconv.Itoa(n), func(b *testing.B) {
   for _, implementation := range implementations {
    b.Run(implementation.name, func(b *testing.B) {
     b.Run("same", func(b *testing.B) {
      f(b, implementation.f, samePath[:n])
     })
     b.Run("unique", func(b *testing.B) {
      f(b, implementation.f, uniquePaths[:n])
     })
    })
   }
  })
 }
}

[Dauer der Verarbeitung: 0.6 Sekunden, vorverarbeitet 2026-06-28]

                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Quellcodebibliothek
     Eigene Quellcodes
     Fremde Quellcodes
     Suchen

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....
    

Besucherstatistik

Besucherstatistik