def test_sum_calls_aggregate_with_correct_params(self, mock_aggregate): # Arrange dist_data = PrivateRDDTest.sc.parallelize([(1, 1.0, "pk1"), (2, 2.0, "pk1")]) mock_aggregate.return_value = PrivateRDDTest.sc.parallelize([(3.0, ["pk1"])]) budget_accountant = budget_accounting.NaiveBudgetAccountant(1, 1e-10) def privacy_id_extractor(x): return x[1] prdd = private_spark.make_private(dist_data, budget_accountant, privacy_id_extractor) sum_params = agg.SumParams(noise_kind=pipeline_dp.NoiseKind.GAUSSIAN, max_partitions_contributed=2, max_contributions_per_partition=3, min_value=1.55, max_value=2.7889, budget_weight=1.1, public_partitions=None, partition_extractor=lambda x: x[0], value_extractor=lambda x: x) # Act actual_result = prdd.sum(sum_params) # Assert mock_aggregate.assert_called_once() args = mock_aggregate.call_args[0] rdd = dist_data.map(lambda x: (privacy_id_extractor(x), x)) self.assertListEqual(args[0].collect(), rdd.collect()) params = pipeline_dp.AggregateParams( noise_kind=pipeline_dp.NoiseKind.GAUSSIAN, metrics=[pipeline_dp.Metrics.SUM], max_partitions_contributed=sum_params.max_partitions_contributed, max_contributions_per_partition=sum_params. max_contributions_per_partition, min_value=sum_params.min_value, max_value=sum_params.max_value, budget_weight=sum_params.budget_weight, public_partitions=sum_params.public_partitions) self.assertEqual(args[1], params) self.assertEqual(actual_result.collect(), [(3.0, "pk1")])
def test_sum_calls_aggregate_with_params(self, mock_aggregate): runner = fn_api_runner.FnApiRunner() with beam.Pipeline(runner=runner) as pipeline: # Arrange pcol = pipeline | 'Create produce' >> beam.Create( float(i) for i in range(1, 7)) budget_accountant = budget_accounting.NaiveBudgetAccountant( total_epsilon=1, total_delta=0.01) private_collection = ( pcol | 'Create private collection' >> private_beam.MakePrivate( budget_accountant=budget_accountant, privacy_id_extractor=PrivateBeamTest.privacy_id_extractor)) sum_params = aggregate_params.SumParams( noise_kind=pipeline_dp.NoiseKind.GAUSSIAN, max_partitions_contributed=2, max_contributions_per_partition=3, min_value=1, max_value=5, budget_weight=1, public_partitions=[], partition_extractor=lambda x: f"pk:{x // 10}", value_extractor=lambda x: x) # Act transformer = private_beam.Sum(sum_params=sum_params) private_collection | transformer # Assert self.assertEqual(transformer._budget_accountant, budget_accountant) mock_aggregate.assert_called_once() args = mock_aggregate.call_args[0] params = pipeline_dp.AggregateParams( noise_kind=pipeline_dp.NoiseKind.GAUSSIAN, metrics=[pipeline_dp.Metrics.SUM], max_partitions_contributed=sum_params. max_partitions_contributed, max_contributions_per_partition=sum_params. max_contributions_per_partition, min_value=sum_params.min_value, max_value=sum_params.max_value, public_partitions=sum_params.public_partitions) self.assertEqual(params, args[1])
def test_sum_calls_with_public_partitions_returns_sensible_result(self): # Arrange col = [(f"{u}", "pubK1", 100.0) for u in range(30)] col += [(f"{u + 30}", "pubK1", -100.0) for u in range(30)] col += [(f"{u + 60}", "privK1", 100.0) for u in range(30)] dist_data = PrivateRDDTest.sc.parallelize(col) # Use very high epsilon and delta to minimize noise and test # flakiness. budget_accountant = budget_accounting.NaiveBudgetAccountant( total_epsilon=800, total_delta=0.999) def privacy_id_extractor(x): return x[0] prdd = private_spark.make_private(dist_data, budget_accountant, privacy_id_extractor) sum_params = agg.SumParams(noise_kind=pipeline_dp.NoiseKind.GAUSSIAN, max_partitions_contributed=2, max_contributions_per_partition=3, min_value=1.55, max_value=2.7889, budget_weight=1, partition_extractor=lambda x: x[1], value_extractor=lambda x: x[2], public_partitions=["pubK1", "pubK2"]) # Act actual_result = prdd.sum(sum_params) budget_accountant.compute_budgets() # Assert # This is a health check to validate that the result is sensible. # Hence, we use a very large tolerance to reduce test flakiness. expected_result_dict = {"pubK2": 0.0, "pubK1": 130.167} actual_result_dict = self.to_dict(actual_result.collect()) for pk, sum in actual_result_dict.items(): self.assertTrue( self.value_per_key_within_tolerance(sum, expected_result_dict[pk], 5.0))
def test_sum_with_public_partitions_returns_sensible_result(self): with TestPipeline() as pipeline: # Arrange col = [(f"{u}", "pubK1", 100.0) for u in range(30)] col += [(f"{u + 30}", "pubK1", -100.0) for u in range(30)] col += [(f"{u + 60}", "privK1", 100.0) for u in range(30)] pcol = pipeline | 'Create produce' >> beam.Create(col) # Use very high epsilon and delta to minimize noise and test # flakiness. budget_accountant = budget_accounting.NaiveBudgetAccountant( total_epsilon=800, total_delta=0.999) private_collection = ( pcol | 'Create private collection' >> private_beam.MakePrivate( budget_accountant=budget_accountant, privacy_id_extractor=lambda x: x[0])) sum_params = aggregate_params.SumParams( noise_kind=pipeline_dp.NoiseKind.GAUSSIAN, max_partitions_contributed=2, max_contributions_per_partition=3, min_value=1.55, max_value=2.7889, budget_weight=1, partition_extractor=lambda x: x[1], value_extractor=lambda x: x[2], public_partitions=["pubK1", "pubK2"]) # Act result = private_collection | private_beam.Sum( sum_params=sum_params) budget_accountant.compute_budgets() # Assert # This is a health check to validate that the result is sensible. # Hence, we use a very large tolerance to reduce test flakiness. beam_util.assert_that( result, beam_util.equal_to([("pubK1", 130.167), ("pubK2", 0.0)], equals_fn=lambda e, a: PrivateBeamTest. value_per_key_within_tolerance(e, a, 10.0)))