View Javadoc
1   /*
2    * Copyright (C) 2019 Google LLC and others
3    *
4    * This program and the accompanying materials are made available under the
5    * terms of the Eclipse Distribution License v. 1.0 which is available at
6    * https://www.eclipse.org/org/documents/edl-v10.php.
7    *
8    * SPDX-License-Identifier: BSD-3-Clause
9    */
10  
11  package org.eclipse.jgit.internal.storage.file;
12  
13  import static org.eclipse.jgit.lib.Ref.Storage.PACKED;
14  import static org.junit.Assert.assertEquals;
15  import static org.junit.Assert.assertThrows;
16  import static org.junit.Assert.assertTrue;
17  
18  import java.io.File;
19  import java.io.FileNotFoundException;
20  import java.io.IOException;
21  import java.nio.file.Files;
22  import java.nio.file.Path;
23  import java.util.Collections;
24  import java.util.List;
25  import java.util.stream.Collectors;
26  
27  import org.eclipse.jgit.internal.storage.file.FileReftableStack.Segment;
28  import org.eclipse.jgit.internal.storage.reftable.MergedReftable;
29  import org.eclipse.jgit.internal.storage.reftable.RefCursor;
30  import org.eclipse.jgit.lib.Config;
31  import org.eclipse.jgit.lib.ObjectId;
32  import org.eclipse.jgit.lib.ObjectIdRef;
33  import org.eclipse.jgit.lib.Ref;
34  import org.eclipse.jgit.util.FileUtils;
35  import org.junit.After;
36  import org.junit.Before;
37  import org.junit.Test;
38  
39  public class FileReftableStackTest {
40  
41  	private static Ref newRef(String name, ObjectId id) {
42  		return new ObjectIdRef.PeeledNonTag(PACKED, name, id);
43  	}
44  
45  	private File reftableDir;
46  
47  	@Before
48  	public void setup() throws Exception {
49  		reftableDir = FileUtils.createTempDir("rtstack", "", null);
50  	}
51  
52  	@After
53  	public void tearDown() throws Exception {
54  		if (reftableDir != null) {
55  			FileUtils.delete(reftableDir, FileUtils.RECURSIVE);
56  		}
57  	}
58  
59  	void writeBranches(FileReftableStack stack, String template, int start,
60  			int N) throws IOException {
61  		for (int i = 0; i < N; i++) {
62  			while (true) {
63  				final long next = stack.getMergedReftable().maxUpdateIndex()
64  						+ 1;
65  
66  				String name = String.format(template,
67  						Integer.valueOf(start + i));
68  				Ref r = newRef(name, ObjectId.zeroId());
69  				boolean ok = stack.addReftable(rw -> {
70  					rw.setMinUpdateIndex(next).setMaxUpdateIndex(next).begin()
71  							.writeRef(r);
72  				});
73  				if (ok) {
74  					break;
75  				}
76  			}
77  		}
78  	}
79  
80  	public void testCompaction(int N) throws Exception {
81  		try (FileReftableStack stack = new FileReftableStack(
82  				new File(reftableDir, "refs"), reftableDir, null,
83  				() -> new Config())) {
84  			writeBranches(stack, "refs/heads/branch%d", 0, N);
85  			MergedReftable table = stack.getMergedReftable();
86  			for (int i = 1; i < N; i++) {
87  				String name = String.format("refs/heads/branch%d",
88  						Integer.valueOf(i));
89  				RefCursor c = table.seekRef(name);
90  				assertTrue(c.next());
91  				assertEquals(ObjectId.zeroId(), c.getRef().getObjectId());
92  			}
93  
94  			List<String> files = Files.list(reftableDir.toPath())
95  					.map(Path::getFileName).map(Path::toString)
96  					.collect(Collectors.toList());
97  			Collections.sort(files);
98  
99  			assertTrue(files.size() < 20);
100 
101 			FileReftableStack.CompactionStats stats = stack.getStats();
102 			assertEquals(0, stats.failed);
103 			assertTrue(stats.attempted < N);
104 			assertTrue(stats.refCount < FileReftableStack.log(N) * N);
105 		}
106 	}
107 
108 	@Test
109 	public void testCompaction9() throws Exception {
110 		testCompaction(9);
111 	}
112 
113 	@Test
114 	public void testCompaction1024() throws Exception {
115 		testCompaction(1024);
116 	}
117 
118 	@SuppressWarnings({ "resource", "unused" })
119 	@Test
120 	public void missingReftable() throws Exception {
121 		try (FileReftableStack stack = new FileReftableStack(
122 				new File(reftableDir, "refs"), reftableDir, null,
123 				() -> new Config())) {
124 			outer: for (int i = 0; i < 10; i++) {
125 				final long next = stack.getMergedReftable().maxUpdateIndex()
126 						+ 1;
127 				String name = String.format("branch%d", Integer.valueOf(i));
128 				Ref r = newRef(name, ObjectId.zeroId());
129 				boolean ok = stack.addReftable(rw -> {
130 					rw.setMinUpdateIndex(next).setMaxUpdateIndex(next).begin()
131 							.writeRef(r);
132 				});
133 				assertTrue(ok);
134 
135 				List<Path> files = Files.list(reftableDir.toPath())
136 						.collect(Collectors.toList());
137 				for (int j = 0; j < files.size(); j++) {
138 					Path f = files.get(j);
139 					Path fileName = f.getFileName();
140 					if (fileName != null
141 							&& fileName.toString().endsWith(".ref")) {
142 						Files.delete(f);
143 						break outer;
144 					}
145 				}
146 			}
147 		}
148 		assertThrows(FileNotFoundException.class,
149 				() -> new FileReftableStack(new File(reftableDir, "refs"),
150 						reftableDir, null, () -> new Config()));
151 	}
152 
153 	@Test
154 	public void testSegments() {
155 		long in[] = { 1024, 1024, 1536, 100, 64, 50, 25, 24 };
156 		List<Segment> got = FileReftableStack.segmentSizes(in);
157 		Segment want[] = { new Segment(0, 3, 10, 3584),
158 				new Segment(3, 5, 6, 164), new Segment(5, 6, 5, 50),
159 				new Segment(6, 8, 4, 49), };
160 		assertEquals(got.size(), want.length);
161 		for (int i = 0; i < want.length; i++) {
162 			assertTrue(want[i].equals(got.get(i)));
163 		}
164 	}
165 
166 	@Test
167 	public void testLog2() throws Exception {
168 		assertEquals(10, FileReftableStack.log(1024));
169 		assertEquals(10, FileReftableStack.log(1025));
170 		assertEquals(10, FileReftableStack.log(2047));
171 	}
172 }